1 package com.google.inject.internal; 2 3 import static com.google.inject.internal.Element.Type.MULTIBINDER; 4 import static com.google.inject.internal.Errors.checkConfiguration; 5 import static com.google.inject.internal.Errors.checkNotNull; 6 import static com.google.inject.name.Names.named; 7 8 import com.google.common.base.Objects; 9 import com.google.common.collect.ImmutableList; 10 import com.google.common.collect.ImmutableSet; 11 import com.google.common.collect.Lists; 12 import com.google.common.collect.Sets; 13 import com.google.inject.AbstractModule; 14 import com.google.inject.Binder; 15 import com.google.inject.Binding; 16 import com.google.inject.Injector; 17 import com.google.inject.Key; 18 import com.google.inject.Module; 19 import com.google.inject.Provider; 20 import com.google.inject.TypeLiteral; 21 import com.google.inject.binder.LinkedBindingBuilder; 22 import com.google.inject.internal.InternalProviderInstanceBindingImpl.InitializationTiming; 23 import com.google.inject.multibindings.MultibinderBinding; 24 import com.google.inject.multibindings.MultibindingsTargetVisitor; 25 import com.google.inject.spi.BindingTargetVisitor; 26 import com.google.inject.spi.Dependency; 27 import com.google.inject.spi.ProviderInstanceBinding; 28 import com.google.inject.spi.ProviderWithExtensionVisitor; 29 import com.google.inject.util.Types; 30 import java.lang.reflect.Type; 31 import java.util.Collection; 32 import java.util.List; 33 import java.util.Set; 34 35 /** 36 * The actual multibinder plays several roles: 37 * 38 * <p>As a Multibinder, it acts as a factory for LinkedBindingBuilders for each of the set's 39 * elements. Each binding is given an annotation that identifies it as a part of this set. 40 * 41 * <p>As a Module, it installs the binding to the set itself. As a module, this implements equals() 42 * and hashcode() in order to trick Guice into executing its configure() method only once. That 43 * makes it so that multiple multibinders can be created for the same target collection, but only 44 * one is bound. Since the list of bindings is retrieved from the injector itself (and not the 45 * multibinder), each multibinder has access to all contributions from all multibinders. 46 * 47 * <p>As a Provider, this constructs the set instances. 48 * 49 * <p>We use a subclass to hide 'implements Module, Provider' from the public API. 50 */ 51 public final class RealMultibinder<T> implements Module { 52 53 /** Implementation of newSetBinder. */ newRealSetBinder(Binder binder, Key<T> key)54 public static <T> RealMultibinder<T> newRealSetBinder(Binder binder, Key<T> key) { 55 binder = binder.skipSources(RealMultibinder.class); 56 RealMultibinder<T> result = new RealMultibinder<>(binder, key); 57 binder.install(result); 58 return result; 59 } 60 61 @SuppressWarnings("unchecked") // wrapping a T in a Set safely returns a Set<T> setOf(TypeLiteral<T> elementType)62 static <T> TypeLiteral<Set<T>> setOf(TypeLiteral<T> elementType) { 63 Type type = Types.setOf(elementType.getType()); 64 return (TypeLiteral<Set<T>>) TypeLiteral.get(type); 65 } 66 67 @SuppressWarnings("unchecked") collectionOfProvidersOf( TypeLiteral<T> elementType)68 static <T> TypeLiteral<Collection<Provider<T>>> collectionOfProvidersOf( 69 TypeLiteral<T> elementType) { 70 Type providerType = Types.providerOf(elementType.getType()); 71 Type type = Types.collectionOf(providerType); 72 return (TypeLiteral<Collection<Provider<T>>>) TypeLiteral.get(type); 73 } 74 75 @SuppressWarnings("unchecked") collectionOfJavaxProvidersOf( TypeLiteral<T> elementType)76 static <T> TypeLiteral<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersOf( 77 TypeLiteral<T> elementType) { 78 Type providerType = 79 Types.newParameterizedType(javax.inject.Provider.class, elementType.getType()); 80 Type type = Types.collectionOf(providerType); 81 return (TypeLiteral<Collection<javax.inject.Provider<T>>>) TypeLiteral.get(type); 82 } 83 84 private final BindingSelection<T> bindingSelection; 85 private final Binder binder; 86 RealMultibinder(Binder binder, Key<T> key)87 RealMultibinder(Binder binder, Key<T> key) { 88 this.binder = checkNotNull(binder, "binder"); 89 this.bindingSelection = new BindingSelection<>(key); 90 } 91 92 @Override configure(Binder binder)93 public void configure(Binder binder) { 94 checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized"); 95 binder 96 .bind(bindingSelection.getSetKey()) 97 .toProvider(new RealMultibinderProvider<T>(bindingSelection)); 98 Provider<Collection<Provider<T>>> collectionOfProvidersProvider = 99 new RealMultibinderCollectionOfProvidersProvider<T>(bindingSelection); 100 binder 101 .bind(bindingSelection.getCollectionOfProvidersKey()) 102 .toProvider(collectionOfProvidersProvider); 103 104 // The collection this exposes is internally an ImmutableList, so it's OK to massage 105 // the guice Provider to javax Provider in the value (since the guice Provider implements 106 // javax Provider). 107 @SuppressWarnings("unchecked") 108 Provider<Collection<javax.inject.Provider<T>>> javaxProvider = 109 (Provider) collectionOfProvidersProvider; 110 binder.bind(bindingSelection.getCollectionOfJavaxProvidersKey()).toProvider(javaxProvider); 111 } 112 permitDuplicates()113 public void permitDuplicates() { 114 binder.install(new PermitDuplicatesModule(bindingSelection.getPermitDuplicatesKey())); 115 } 116 117 /** Adds a new entry to the set and returns the key for it. */ getKeyForNewItem()118 Key<T> getKeyForNewItem() { 119 checkConfiguration(!bindingSelection.isInitialized(), "Multibinder was already initialized"); 120 return Key.get( 121 bindingSelection.getElementTypeLiteral(), 122 new RealElement(bindingSelection.getSetName(), MULTIBINDER, "")); 123 } 124 addBinding()125 public LinkedBindingBuilder<T> addBinding() { 126 return binder.bind(getKeyForNewItem()); 127 } 128 129 // These methods are used by RealMapBinder 130 getSetKey()131 Key<Set<T>> getSetKey() { 132 return bindingSelection.getSetKey(); 133 } 134 getElementTypeLiteral()135 TypeLiteral<T> getElementTypeLiteral() { 136 return bindingSelection.getElementTypeLiteral(); 137 } 138 getSetName()139 String getSetName() { 140 return bindingSelection.getSetName(); 141 } 142 permitsDuplicates(Injector injector)143 boolean permitsDuplicates(Injector injector) { 144 return bindingSelection.permitsDuplicates(injector); 145 } 146 containsElement(com.google.inject.spi.Element element)147 boolean containsElement(com.google.inject.spi.Element element) { 148 return bindingSelection.containsElement(element); 149 } 150 151 private static final class RealMultibinderProvider<T> 152 extends InternalProviderInstanceBindingImpl.Factory<Set<T>> 153 implements ProviderWithExtensionVisitor<Set<T>>, MultibinderBinding<Set<T>> { 154 private final BindingSelection<T> bindingSelection; 155 private List<Binding<T>> bindings; 156 private SingleParameterInjector<T>[] injectors; 157 private boolean permitDuplicates; 158 RealMultibinderProvider(BindingSelection<T> bindingSelection)159 RealMultibinderProvider(BindingSelection<T> bindingSelection) { 160 // While Multibinders only depend on bindings created in modules so we could theoretically 161 // initialize eagerly, they also depend on 162 // 1. findBindingsByType returning results 163 // 2. being able to call BindingImpl.acceptTargetVisitor 164 // neither of those is available during eager initialization, so we use DELAYED 165 super(InitializationTiming.DELAYED); 166 this.bindingSelection = bindingSelection; 167 } 168 169 @Override getDependencies()170 public Set<Dependency<?>> getDependencies() { 171 return bindingSelection.getDependencies(); 172 } 173 174 @Override initialize(InjectorImpl injector, Errors errors)175 void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { 176 bindingSelection.initialize(injector, errors); 177 this.bindings = bindingSelection.getBindings(); 178 this.injectors = bindingSelection.getParameterInjectors(); 179 this.permitDuplicates = bindingSelection.permitsDuplicates(); 180 } 181 182 @Override doProvision(InternalContext context, Dependency<?> dependency)183 protected Set<T> doProvision(InternalContext context, Dependency<?> dependency) 184 throws InternalProvisionException { 185 SingleParameterInjector<T>[] localInjectors = injectors; 186 if (localInjectors == null) { 187 // if localInjectors == null, then we have no bindings so return the empty set. 188 return ImmutableSet.of(); 189 } 190 // Ideally we would just add to an ImmutableSet.Builder, but if we did that and there were 191 // duplicates we wouldn't be able to tell which one was the duplicate. So to manage this we 192 // first put everything into an array and then construct the set. This way if something gets 193 // dropped we can figure out what it is. 194 @SuppressWarnings("unchecked") 195 T[] values = (T[]) new Object[localInjectors.length]; 196 for (int i = 0; i < localInjectors.length; i++) { 197 SingleParameterInjector<T> parameterInjector = localInjectors[i]; 198 T newValue = parameterInjector.inject(context); 199 if (newValue == null) { 200 throw newNullEntryException(i); 201 } 202 values[i] = newValue; 203 } 204 ImmutableSet<T> set = ImmutableSet.copyOf(values); 205 // There are fewer items in the set than the array. Figure out which one got dropped. 206 if (!permitDuplicates && set.size() < values.length) { 207 throw newDuplicateValuesException(set, values); 208 } 209 return set; 210 } 211 newNullEntryException(int i)212 private InternalProvisionException newNullEntryException(int i) { 213 return InternalProvisionException.create( 214 "Set injection failed due to null element bound at: %s", bindings.get(i).getSource()); 215 } 216 217 @SuppressWarnings("unchecked") 218 @Override acceptExtensionVisitor( BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding)219 public <B, V> V acceptExtensionVisitor( 220 BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) { 221 if (visitor instanceof MultibindingsTargetVisitor) { 222 return ((MultibindingsTargetVisitor<Set<T>, V>) visitor).visit(this); 223 } else { 224 return visitor.visit(binding); 225 } 226 } 227 newDuplicateValuesException( ImmutableSet<T> set, T[] values)228 private InternalProvisionException newDuplicateValuesException( 229 ImmutableSet<T> set, T[] values) { 230 // TODO(lukes): consider reporting all duplicate values, the easiest way would be to rebuild 231 // a new set and detect dupes as we go 232 // Find the duplicate binding 233 // To do this we take advantage of the fact that set, values and bindings all have the same 234 // ordering for a non-empty prefix of the set. 235 // First we scan for the first item dropped from the set. 236 int newBindingIndex = 0; 237 for (T item : set) { 238 if (item != values[newBindingIndex]) { 239 break; 240 } 241 newBindingIndex++; 242 } 243 // once we exit the loop newBindingIndex will point at the first item in values that was 244 // dropped. 245 246 Binding<T> newBinding = bindings.get(newBindingIndex); 247 T newValue = values[newBindingIndex]; 248 // Now we scan again to find the index of the value, we are guaranteed to find it. 249 int oldBindingIndex = set.asList().indexOf(newValue); 250 T oldValue = values[oldBindingIndex]; 251 Binding<T> duplicateBinding = bindings.get(oldBindingIndex); 252 String oldString = oldValue.toString(); 253 String newString = newValue.toString(); 254 if (Objects.equal(oldString, newString)) { 255 // When the value strings match, just show the source of the bindings 256 return InternalProvisionException.create( 257 "Set injection failed due to duplicated element \"%s\"" 258 + "\n Bound at %s\n Bound at %s", 259 newValue, duplicateBinding.getSource(), newBinding.getSource()); 260 } else { 261 // When the value strings don't match, include them both as they may be useful for debugging 262 return InternalProvisionException.create( 263 "Set injection failed due to multiple elements comparing equal:" 264 + "\n \"%s\"\n bound at %s" 265 + "\n \"%s\"\n bound at %s", 266 oldValue, duplicateBinding.getSource(), newValue, newBinding.getSource()); 267 } 268 } 269 270 @Override equals(Object obj)271 public boolean equals(Object obj) { 272 return obj instanceof RealMultibinderProvider 273 && bindingSelection.equals(((RealMultibinderProvider<?>) obj).bindingSelection); 274 } 275 276 @Override hashCode()277 public int hashCode() { 278 return bindingSelection.hashCode(); 279 } 280 281 @Override getSetKey()282 public Key<Set<T>> getSetKey() { 283 return bindingSelection.getSetKey(); 284 } 285 286 @Override getElementTypeLiteral()287 public TypeLiteral<?> getElementTypeLiteral() { 288 return bindingSelection.getElementTypeLiteral(); 289 } 290 291 @Override getElements()292 public List<Binding<?>> getElements() { 293 return bindingSelection.getElements(); 294 } 295 296 @Override permitsDuplicates()297 public boolean permitsDuplicates() { 298 return bindingSelection.permitsDuplicates(); 299 } 300 301 @Override containsElement(com.google.inject.spi.Element element)302 public boolean containsElement(com.google.inject.spi.Element element) { 303 return bindingSelection.containsElement(element); 304 } 305 } 306 307 private static final class BindingSelection<T> { 308 // prior to initialization we declare just a dependency on the injector, but as soon as we are 309 // initialized we swap to dependencies on the elements. 310 private static final ImmutableSet<Dependency<?>> MODULE_DEPENDENCIES = 311 ImmutableSet.<Dependency<?>>of(Dependency.get(Key.get(Injector.class))); 312 private final TypeLiteral<T> elementType; 313 private final Key<Set<T>> setKey; 314 315 // these are all lazily allocated 316 private String setName; 317 private Key<Collection<Provider<T>>> collectionOfProvidersKey; 318 private Key<Collection<javax.inject.Provider<T>>> collectionOfJavaxProvidersKey; 319 private Key<Boolean> permitDuplicatesKey; 320 321 private boolean isInitialized; 322 /* a binding for each element in the set. null until initialization, non-null afterwards */ 323 private ImmutableList<Binding<T>> bindings; 324 325 // Starts out as Injector and gets set up properly after initialization 326 private ImmutableSet<Dependency<?>> dependencies = MODULE_DEPENDENCIES; 327 private ImmutableSet<Dependency<?>> providerDependencies = MODULE_DEPENDENCIES; 328 329 /** whether duplicates are allowed. Possibly configured by a different instance */ 330 private boolean permitDuplicates; 331 332 private SingleParameterInjector<T>[] parameterinjectors; 333 BindingSelection(Key<T> key)334 BindingSelection(Key<T> key) { 335 this.setKey = key.ofType(setOf(key.getTypeLiteral())); 336 this.elementType = key.getTypeLiteral(); 337 } 338 initialize(InjectorImpl injector, Errors errors)339 void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { 340 // This will be called multiple times, once by each Factory. We only want 341 // to do the work to initialize everything once, so guard this code with 342 // isInitialized. 343 if (isInitialized) { 344 return; 345 } 346 List<Binding<T>> bindings = Lists.newArrayList(); 347 Set<Indexer.IndexedBinding> index = Sets.newHashSet(); 348 Indexer indexer = new Indexer(injector); 349 List<Dependency<?>> dependencies = Lists.newArrayList(); 350 List<Dependency<?>> providerDependencies = Lists.newArrayList(); 351 for (Binding<?> entry : injector.findBindingsByType(elementType)) { 352 if (keyMatches(entry.getKey())) { 353 @SuppressWarnings("unchecked") // protected by findBindingsByType() 354 Binding<T> binding = (Binding<T>) entry; 355 if (index.add(binding.acceptTargetVisitor(indexer))) { 356 // TODO(lukes): most of these are linked bindings since user bindings are linked to 357 // a user binding through the @Element annotation. Since this is an implementation 358 // detail we could 'dereference' the @Element if it is a LinkedBinding and avoid 359 // provisioning through the FactoryProxy at runtime. 360 // Ditto for OptionalBinder/MapBinder 361 bindings.add(binding); 362 Key<T> key = binding.getKey(); 363 // TODO(lukes): we should mark this as a non-nullable dependency since we don't accept 364 // null. 365 // Add a dependency on Key<T> 366 dependencies.add(Dependency.get(key)); 367 // and add a dependency on Key<Provider<T>> 368 providerDependencies.add( 369 Dependency.get(key.ofType(Types.providerOf(key.getTypeLiteral().getType())))); 370 } 371 } 372 } 373 374 this.bindings = ImmutableList.copyOf(bindings); 375 this.dependencies = ImmutableSet.copyOf(dependencies); 376 this.providerDependencies = ImmutableSet.copyOf(providerDependencies); 377 this.permitDuplicates = permitsDuplicates(injector); 378 // This is safe because all our dependencies are assignable to T and we never assign to 379 // elements of this array. 380 @SuppressWarnings("unchecked") 381 SingleParameterInjector<T>[] typed = 382 (SingleParameterInjector<T>[]) injector.getParametersInjectors(dependencies, errors); 383 this.parameterinjectors = typed; 384 isInitialized = true; 385 } 386 permitsDuplicates(Injector injector)387 boolean permitsDuplicates(Injector injector) { 388 return injector.getBindings().containsKey(getPermitDuplicatesKey()); 389 } 390 getBindings()391 ImmutableList<Binding<T>> getBindings() { 392 checkConfiguration(isInitialized, "not initialized"); 393 return bindings; 394 } 395 getParameterInjectors()396 SingleParameterInjector<T>[] getParameterInjectors() { 397 checkConfiguration(isInitialized, "not initialized"); 398 return parameterinjectors; 399 } 400 getDependencies()401 ImmutableSet<Dependency<?>> getDependencies() { 402 return dependencies; 403 } 404 getProviderDependencies()405 ImmutableSet<Dependency<?>> getProviderDependencies() { 406 return providerDependencies; 407 } 408 getSetName()409 String getSetName() { 410 // lazily initialized since most selectors don't survive module installation. 411 if (setName == null) { 412 setName = Annotations.nameOf(setKey); 413 } 414 return setName; 415 } 416 getPermitDuplicatesKey()417 Key<Boolean> getPermitDuplicatesKey() { 418 Key<Boolean> local = permitDuplicatesKey; 419 if (local == null) { 420 local = 421 permitDuplicatesKey = Key.get(Boolean.class, named(toString() + " permits duplicates")); 422 } 423 return local; 424 } 425 getCollectionOfProvidersKey()426 Key<Collection<Provider<T>>> getCollectionOfProvidersKey() { 427 Key<Collection<Provider<T>>> local = collectionOfProvidersKey; 428 if (local == null) { 429 local = collectionOfProvidersKey = setKey.ofType(collectionOfProvidersOf(elementType)); 430 } 431 return local; 432 } 433 getCollectionOfJavaxProvidersKey()434 Key<Collection<javax.inject.Provider<T>>> getCollectionOfJavaxProvidersKey() { 435 Key<Collection<javax.inject.Provider<T>>> local = collectionOfJavaxProvidersKey; 436 if (local == null) { 437 local = 438 collectionOfJavaxProvidersKey = 439 setKey.ofType(collectionOfJavaxProvidersOf(elementType)); 440 } 441 return local; 442 } 443 isInitialized()444 boolean isInitialized() { 445 return isInitialized; 446 } 447 448 // MultibinderBinding API methods 449 getElementTypeLiteral()450 TypeLiteral<T> getElementTypeLiteral() { 451 return elementType; 452 } 453 getSetKey()454 Key<Set<T>> getSetKey() { 455 return setKey; 456 } 457 458 @SuppressWarnings("unchecked") getElements()459 List<Binding<?>> getElements() { 460 if (isInitialized()) { 461 return (List<Binding<?>>) (List<?>) bindings; // safe because bindings is immutable. 462 } else { 463 throw new UnsupportedOperationException("getElements() not supported for module bindings"); 464 } 465 } 466 permitsDuplicates()467 boolean permitsDuplicates() { 468 if (isInitialized()) { 469 return permitDuplicates; 470 } else { 471 throw new UnsupportedOperationException( 472 "permitsDuplicates() not supported for module bindings"); 473 } 474 } 475 containsElement(com.google.inject.spi.Element element)476 boolean containsElement(com.google.inject.spi.Element element) { 477 if (element instanceof Binding) { 478 Binding<?> binding = (Binding<?>) element; 479 return keyMatches(binding.getKey()) 480 || binding.getKey().equals(getPermitDuplicatesKey()) 481 || binding.getKey().equals(setKey) 482 || binding.getKey().equals(collectionOfProvidersKey) 483 || binding.getKey().equals(collectionOfJavaxProvidersKey); 484 } else { 485 return false; 486 } 487 } 488 keyMatches(Key<?> key)489 private boolean keyMatches(Key<?> key) { 490 return key.getTypeLiteral().equals(elementType) 491 && key.getAnnotation() instanceof Element 492 && ((Element) key.getAnnotation()).setName().equals(getSetName()) 493 && ((Element) key.getAnnotation()).type() == MULTIBINDER; 494 } 495 496 @Override equals(Object obj)497 public boolean equals(Object obj) { 498 if (obj instanceof BindingSelection) { 499 return setKey.equals(((BindingSelection<?>) obj).setKey); 500 } 501 return false; 502 } 503 504 @Override hashCode()505 public int hashCode() { 506 return setKey.hashCode(); 507 } 508 509 @Override toString()510 public String toString() { 511 return (getSetName().isEmpty() ? "" : getSetName() + " ") 512 + "Multibinder<" 513 + elementType 514 + ">"; 515 } 516 } 517 518 @Override equals(Object o)519 public boolean equals(Object o) { 520 return o instanceof RealMultibinder 521 && ((RealMultibinder<?>) o).bindingSelection.equals(bindingSelection); 522 } 523 524 @Override hashCode()525 public int hashCode() { 526 return bindingSelection.hashCode(); 527 } 528 529 private static final class RealMultibinderCollectionOfProvidersProvider<T> 530 extends InternalProviderInstanceBindingImpl.Factory<Collection<Provider<T>>> { 531 532 private final BindingSelection<T> bindingSelection; 533 private ImmutableList<Provider<T>> collectionOfProviders; 534 RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection)535 RealMultibinderCollectionOfProvidersProvider(BindingSelection<T> bindingSelection) { 536 super(InitializationTiming.DELAYED); // See comment in RealMultibinderProvider 537 this.bindingSelection = bindingSelection; 538 } 539 540 @Override initialize(InjectorImpl injector, Errors errors)541 void initialize(InjectorImpl injector, Errors errors) throws ErrorsException { 542 bindingSelection.initialize(injector, errors); 543 ImmutableList.Builder<Provider<T>> providers = ImmutableList.builder(); 544 for (Binding<T> binding : bindingSelection.getBindings()) { 545 providers.add(binding.getProvider()); 546 } 547 this.collectionOfProviders = providers.build(); 548 } 549 550 @Override doProvision( InternalContext context, Dependency<?> dependency)551 protected Collection<Provider<T>> doProvision( 552 InternalContext context, Dependency<?> dependency) { 553 return collectionOfProviders; 554 } 555 556 @Override getDependencies()557 public Set<Dependency<?>> getDependencies() { 558 return bindingSelection.getProviderDependencies(); 559 } 560 561 @Override equals(Object obj)562 public boolean equals(Object obj) { 563 return obj instanceof RealMultibinderCollectionOfProvidersProvider 564 && bindingSelection.equals( 565 ((RealMultibinderCollectionOfProvidersProvider<?>) obj).bindingSelection); 566 } 567 568 @Override hashCode()569 public int hashCode() { 570 return bindingSelection.hashCode(); 571 } 572 } 573 574 /** 575 * We install the permit duplicates configuration as its own binding, all by itself. This way, if 576 * only one of a multibinder's users remember to call permitDuplicates(), they're still permitted. 577 * 578 * <p>This is like setting a global variable in the injector so that each instance of the 579 * multibinder will have the same value for permitDuplicates, even if it is only set on one of 580 * them. 581 */ 582 private static class PermitDuplicatesModule extends AbstractModule { 583 private final Key<Boolean> key; 584 PermitDuplicatesModule(Key<Boolean> key)585 PermitDuplicatesModule(Key<Boolean> key) { 586 this.key = key; 587 } 588 589 @Override configure()590 protected void configure() { 591 bind(key).toInstance(true); 592 } 593 594 @Override equals(Object o)595 public boolean equals(Object o) { 596 return o instanceof PermitDuplicatesModule && ((PermitDuplicatesModule) o).key.equals(key); 597 } 598 599 @Override hashCode()600 public int hashCode() { 601 return getClass().hashCode() ^ key.hashCode(); 602 } 603 } 604 } 605