1 /* 2 * Copyright (C) 2010 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; 18 19 import static com.google.inject.Asserts.*; 20 import static com.google.inject.name.Names.named; 21 22 import com.google.common.base.Objects; 23 import com.google.common.collect.Lists; 24 import com.google.inject.name.Named; 25 import com.google.inject.spi.Element; 26 import com.google.inject.spi.Elements; 27 import com.google.inject.util.Providers; 28 import java.lang.annotation.Annotation; 29 import java.lang.reflect.Constructor; 30 import java.util.Arrays; 31 import java.util.Collection; 32 import java.util.LinkedHashSet; 33 import java.util.List; 34 import java.util.logging.Logger; 35 import junit.framework.TestCase; 36 37 /** 38 * A suite of tests for duplicate bindings. 39 * 40 * @author sameb@google.com (Sam Berlin) 41 */ 42 public class DuplicateBindingsTest extends TestCase { 43 44 private FooImpl foo = new FooImpl(); 45 private Provider<Foo> pFoo = Providers.<Foo>of(new FooImpl()); 46 private Class<? extends Provider<? extends Foo>> pclFoo = FooProvider.class; 47 private Class<? extends Foo> clFoo = FooImpl.class; 48 private Constructor<FooImpl> cFoo = FooImpl.cxtor(); 49 testDuplicateBindingsAreIgnored()50 public void testDuplicateBindingsAreIgnored() { 51 Injector injector = 52 Guice.createInjector( 53 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 54 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); 55 List<Key<?>> bindings = Lists.newArrayList(injector.getAllBindings().keySet()); 56 removeBasicBindings(bindings); 57 58 // Ensure only one binding existed for each type. 59 assertTrue(bindings.remove(Key.get(Foo.class, named("instance")))); 60 assertTrue(bindings.remove(Key.get(Foo.class, named("pInstance")))); 61 assertTrue(bindings.remove(Key.get(Foo.class, named("pKey")))); 62 assertTrue(bindings.remove(Key.get(Foo.class, named("linkedKey")))); 63 assertTrue(bindings.remove(Key.get(FooImpl.class))); 64 assertTrue(bindings.remove(Key.get(Foo.class, named("constructor")))); 65 assertTrue(bindings.remove(Key.get(FooProvider.class))); // JIT binding 66 assertTrue(bindings.remove(Key.get(Foo.class, named("providerMethod")))); 67 68 assertEquals(bindings.toString(), 0, bindings.size()); 69 } 70 testElementsDeduplicate()71 public void testElementsDeduplicate() { 72 List<Element> elements = 73 Elements.getElements( 74 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 75 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); 76 assertEquals(14, elements.size()); 77 assertEquals(7, new LinkedHashSet<Element>(elements).size()); 78 } 79 testProviderMethodsFailIfInstancesDiffer()80 public void testProviderMethodsFailIfInstancesDiffer() { 81 try { 82 Guice.createInjector(new FailingProviderModule(), new FailingProviderModule()); 83 fail("should have failed"); 84 } catch (CreationException ce) { 85 assertContains( 86 ce.getMessage(), 87 "A binding to " 88 + Foo.class.getName() 89 + " was already configured " 90 + "at " 91 + FailingProviderModule.class.getName(), 92 "at " + FailingProviderModule.class.getName()); 93 } 94 } 95 testSameScopeInstanceIgnored()96 public void testSameScopeInstanceIgnored() { 97 Guice.createInjector( 98 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), 99 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo)); 100 101 Guice.createInjector( 102 new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo), 103 new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo)); 104 } 105 testSameScopeAnnotationIgnored()106 public void testSameScopeAnnotationIgnored() { 107 Guice.createInjector( 108 new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo), 109 new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo)); 110 } 111 testMixedAnnotationAndScopeForSingletonIgnored()112 public void testMixedAnnotationAndScopeForSingletonIgnored() { 113 Guice.createInjector( 114 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo), 115 new AnnotatedScopeModule(Singleton.class, foo, pFoo, pclFoo, clFoo, cFoo)); 116 } 117 testMixedScopeAndUnscopedIgnored()118 public void testMixedScopeAndUnscopedIgnored() { 119 Guice.createInjector( 120 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 121 new ScopedModule(Scopes.NO_SCOPE, foo, pFoo, pclFoo, clFoo, cFoo)); 122 } 123 testMixedScopeFails()124 public void testMixedScopeFails() { 125 try { 126 Guice.createInjector( 127 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 128 new ScopedModule(Scopes.SINGLETON, foo, pFoo, pclFoo, clFoo, cFoo)); 129 fail("expected exception"); 130 } catch (CreationException ce) { 131 String segment1 = 132 "A binding to " 133 + Foo.class.getName() 134 + " annotated with " 135 + named("pInstance") 136 + " was already configured at " 137 + SimpleModule.class.getName(); 138 String segment2 = 139 "A binding to " 140 + Foo.class.getName() 141 + " annotated with " 142 + named("pKey") 143 + " was already configured at " 144 + SimpleModule.class.getName(); 145 String segment3 = 146 "A binding to " 147 + Foo.class.getName() 148 + " annotated with " 149 + named("constructor") 150 + " was already configured at " 151 + SimpleModule.class.getName(); 152 String segment4 = 153 "A binding to " 154 + FooImpl.class.getName() 155 + " was already configured at " 156 + SimpleModule.class.getName(); 157 String atSegment = "at " + ScopedModule.class.getName(); 158 if (isIncludeStackTraceOff()) { 159 assertContains( 160 ce.getMessage(), 161 segment1, 162 atSegment, 163 segment2, 164 atSegment, 165 segment3, 166 atSegment, 167 segment4, 168 atSegment); 169 } else { 170 assertContains( 171 ce.getMessage(), 172 segment1, 173 atSegment, 174 segment2, 175 atSegment, 176 segment4, 177 atSegment, 178 segment3, 179 atSegment); 180 } 181 } 182 } 183 184 @SuppressWarnings("unchecked") testMixedTargetsFails()185 public void testMixedTargetsFails() { 186 try { 187 Guice.createInjector( 188 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 189 new SimpleModule( 190 new FooImpl(), 191 Providers.<Foo>of(new FooImpl()), 192 (Class) BarProvider.class, 193 (Class) Bar.class, 194 (Constructor) Bar.cxtor())); 195 fail("expected exception"); 196 } catch (CreationException ce) { 197 assertContains( 198 ce.getMessage(), 199 "A binding to " 200 + Foo.class.getName() 201 + " annotated with " 202 + named("pInstance") 203 + " was already configured at " 204 + SimpleModule.class.getName(), 205 "at " + SimpleModule.class.getName(), 206 "A binding to " 207 + Foo.class.getName() 208 + " annotated with " 209 + named("pKey") 210 + " was already configured at " 211 + SimpleModule.class.getName(), 212 "at " + SimpleModule.class.getName(), 213 "A binding to " 214 + Foo.class.getName() 215 + " annotated with " 216 + named("linkedKey") 217 + " was already configured at " 218 + SimpleModule.class.getName(), 219 "at " + SimpleModule.class.getName(), 220 "A binding to " 221 + Foo.class.getName() 222 + " annotated with " 223 + named("constructor") 224 + " was already configured at " 225 + SimpleModule.class.getName(), 226 "at " + SimpleModule.class.getName()); 227 } 228 } 229 testExceptionInEqualsThrowsCreationException()230 public void testExceptionInEqualsThrowsCreationException() { 231 try { 232 Guice.createInjector(new ThrowingModule(), new ThrowingModule()); 233 fail("expected exception"); 234 } catch (CreationException ce) { 235 assertContains( 236 ce.getMessage(), 237 "A binding to " 238 + Foo.class.getName() 239 + " was already configured at " 240 + ThrowingModule.class.getName(), 241 "and an error was thrown while checking duplicate bindings. Error: java.lang.RuntimeException: Boo!", 242 "at " + ThrowingModule.class.getName()); 243 } 244 } 245 testChildInjectorDuplicateParentFail()246 public void testChildInjectorDuplicateParentFail() { 247 Injector injector = Guice.createInjector(new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); 248 249 try { 250 injector.createChildInjector(new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); 251 fail("expected exception"); 252 } catch (CreationException ce) { 253 assertContains( 254 ce.getMessage(), 255 "A binding to " 256 + Foo.class.getName() 257 + " annotated with " 258 + named("pInstance") 259 + " was already configured at " 260 + SimpleModule.class.getName(), 261 "at " + SimpleModule.class.getName(), 262 "A binding to " 263 + Foo.class.getName() 264 + " annotated with " 265 + named("pKey") 266 + " was already configured at " 267 + SimpleModule.class.getName(), 268 "at " + SimpleModule.class.getName(), 269 "A binding to " 270 + Foo.class.getName() 271 + " annotated with " 272 + named("linkedKey") 273 + " was already configured at " 274 + SimpleModule.class.getName(), 275 "at " + SimpleModule.class.getName(), 276 "A binding to " 277 + Foo.class.getName() 278 + " annotated with " 279 + named("constructor") 280 + " was already configured at " 281 + SimpleModule.class.getName(), 282 "at " + SimpleModule.class.getName(), 283 "A binding to " 284 + Foo.class.getName() 285 + " annotated with " 286 + named("providerMethod") 287 + " was already configured at " 288 + SimpleProviderModule.class.getName(), 289 "at " + SimpleProviderModule.class.getName()); 290 } 291 } 292 testDuplicatesSolelyInChildIgnored()293 public void testDuplicatesSolelyInChildIgnored() { 294 Injector injector = Guice.createInjector(); 295 injector.createChildInjector( 296 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo), 297 new SimpleModule(foo, pFoo, pclFoo, clFoo, cFoo)); 298 } 299 testDifferentBindingTypesFail()300 public void testDifferentBindingTypesFail() { 301 List<Element> elements = Elements.getElements(new FailedModule(foo, pFoo, pclFoo, clFoo, cFoo)); 302 303 // Make sure every combination of the elements with another element fails. 304 // This ensures that duplication checks the kind of binding also. 305 for (Element e1 : elements) { 306 for (Element e2 : elements) { 307 // if they're the same, this shouldn't fail. 308 try { 309 Guice.createInjector(Elements.getModule(Arrays.asList(e1, e2))); 310 if (e1 != e2) { 311 fail("must fail!"); 312 } 313 } catch (CreationException expected) { 314 if (e1 != e2) { 315 assertContains( 316 expected.getMessage(), 317 "A binding to " 318 + Foo.class.getName() 319 + " was already configured at " 320 + FailedModule.class.getName(), 321 "at " + FailedModule.class.getName()); 322 } else { 323 throw expected; 324 } 325 } 326 } 327 } 328 } 329 testJitBindingsAreCheckedAfterConversions()330 public void testJitBindingsAreCheckedAfterConversions() { 331 Guice.createInjector( 332 new AbstractModule() { 333 @Override 334 protected void configure() { 335 bind(A.class); 336 bind(A.class).to(RealA.class); 337 } 338 }); 339 } 340 testEqualsNotCalledByDefaultOnInstance()341 public void testEqualsNotCalledByDefaultOnInstance() { 342 final HashEqualsTester a = new HashEqualsTester(); 343 a.throwOnEquals = true; 344 Guice.createInjector( 345 new AbstractModule() { 346 @Override 347 protected void configure() { 348 bind(String.class); 349 bind(HashEqualsTester.class).toInstance(a); 350 } 351 }); 352 } 353 testEqualsNotCalledByDefaultOnProvider()354 public void testEqualsNotCalledByDefaultOnProvider() { 355 final HashEqualsTester a = new HashEqualsTester(); 356 a.throwOnEquals = true; 357 Guice.createInjector( 358 new AbstractModule() { 359 @Override 360 protected void configure() { 361 bind(String.class); 362 bind(Object.class).toProvider(a); 363 } 364 }); 365 } 366 testHashcodeNeverCalledOnInstance()367 public void testHashcodeNeverCalledOnInstance() { 368 final HashEqualsTester a = new HashEqualsTester(); 369 a.throwOnHashcode = true; 370 a.equality = "test"; 371 372 final HashEqualsTester b = new HashEqualsTester(); 373 b.throwOnHashcode = true; 374 b.equality = "test"; 375 Guice.createInjector( 376 new AbstractModule() { 377 @Override 378 protected void configure() { 379 bind(String.class); 380 bind(HashEqualsTester.class).toInstance(a); 381 bind(HashEqualsTester.class).toInstance(b); 382 } 383 }); 384 } 385 testHashcodeNeverCalledOnProviderInstance()386 public void testHashcodeNeverCalledOnProviderInstance() { 387 final HashEqualsTester a = new HashEqualsTester(); 388 a.throwOnHashcode = true; 389 a.equality = "test"; 390 391 final HashEqualsTester b = new HashEqualsTester(); 392 b.throwOnHashcode = true; 393 b.equality = "test"; 394 Guice.createInjector( 395 new AbstractModule() { 396 @Override 397 protected void configure() { 398 bind(String.class); 399 bind(Object.class).toProvider(a); 400 bind(Object.class).toProvider(b); 401 } 402 }); 403 } 404 405 private static class RealA extends A {} 406 407 @ImplementedBy(RealA.class) 408 private static class A {} 409 removeBasicBindings(Collection<Key<?>> bindings)410 private void removeBasicBindings(Collection<Key<?>> bindings) { 411 bindings.remove(Key.get(Injector.class)); 412 bindings.remove(Key.get(Logger.class)); 413 bindings.remove(Key.get(Stage.class)); 414 } 415 416 private static class ThrowingModule extends AbstractModule { 417 @Override configure()418 protected void configure() { 419 bind(Foo.class) 420 .toInstance( 421 new Foo() { 422 @Override 423 public boolean equals(Object obj) { 424 throw new RuntimeException("Boo!"); 425 } 426 427 @Override 428 public int hashCode() { 429 throw new RuntimeException("Boo!"); 430 } 431 }); 432 } 433 } 434 435 private abstract static class FooModule extends AbstractModule { 436 protected final FooImpl foo; 437 protected final Provider<Foo> pFoo; 438 protected final Class<? extends Provider<? extends Foo>> pclFoo; 439 protected final Class<? extends Foo> clFoo; 440 protected final Constructor<FooImpl> cFoo; 441 FooModule( FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)442 FooModule( 443 FooImpl foo, 444 Provider<Foo> pFoo, 445 Class<? extends Provider<? extends Foo>> pclFoo, 446 Class<? extends Foo> clFoo, 447 Constructor<FooImpl> cFoo) { 448 this.foo = foo; 449 this.pFoo = pFoo; 450 this.pclFoo = pclFoo; 451 this.clFoo = clFoo; 452 this.cFoo = cFoo; 453 } 454 } 455 456 private static class FailedModule extends FooModule { FailedModule( FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)457 FailedModule( 458 FooImpl foo, 459 Provider<Foo> pFoo, 460 Class<? extends Provider<? extends Foo>> pclFoo, 461 Class<? extends Foo> clFoo, 462 Constructor<FooImpl> cFoo) { 463 super(foo, pFoo, pclFoo, clFoo, cFoo); 464 } 465 466 @Override configure()467 protected void configure() { 468 // InstanceBinding 469 bind(Foo.class).toInstance(foo); 470 471 // ProviderInstanceBinding 472 bind(Foo.class).toProvider(pFoo); 473 474 // ProviderKeyBinding 475 bind(Foo.class).toProvider(pclFoo); 476 477 // LinkedKeyBinding 478 bind(Foo.class).to(clFoo); 479 480 // ConstructorBinding 481 bind(Foo.class).toConstructor(cFoo); 482 } 483 484 @Provides foo()485 Foo foo() { 486 return null; 487 } 488 } 489 490 private static class FailingProviderModule extends AbstractModule { 491 492 @Provides foo()493 Foo foo() { 494 return null; 495 } 496 } 497 498 private static class SimpleProviderModule extends AbstractModule { 499 500 @Provides 501 @Named("providerMethod") foo()502 Foo foo() { 503 return null; 504 } 505 506 @Override equals(Object obj)507 public boolean equals(Object obj) { 508 return obj.getClass() == getClass(); 509 } 510 } 511 512 private static class SimpleModule extends FooModule { SimpleModule( FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)513 SimpleModule( 514 FooImpl foo, 515 Provider<Foo> pFoo, 516 Class<? extends Provider<? extends Foo>> pclFoo, 517 Class<? extends Foo> clFoo, 518 Constructor<FooImpl> cFoo) { 519 super(foo, pFoo, pclFoo, clFoo, cFoo); 520 } 521 522 @Override configure()523 protected void configure() { 524 // InstanceBinding 525 bind(Foo.class).annotatedWith(named("instance")).toInstance(foo); 526 527 // ProviderInstanceBinding 528 bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo); 529 530 // ProviderKeyBinding 531 bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo); 532 533 // LinkedKeyBinding 534 bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo); 535 536 // UntargettedBinding / ConstructorBinding 537 bind(FooImpl.class); 538 539 // ConstructorBinding 540 bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo); 541 542 // ProviderMethod 543 // (reconstructed from an Element to ensure it doesn't get filtered out 544 // by deduplicating Modules) 545 install(Elements.getModule(Elements.getElements(new SimpleProviderModule()))); 546 } 547 } 548 549 private static class ScopedModule extends FooModule { 550 private final Scope scope; 551 ScopedModule( Scope scope, FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)552 ScopedModule( 553 Scope scope, 554 FooImpl foo, 555 Provider<Foo> pFoo, 556 Class<? extends Provider<? extends Foo>> pclFoo, 557 Class<? extends Foo> clFoo, 558 Constructor<FooImpl> cFoo) { 559 super(foo, pFoo, pclFoo, clFoo, cFoo); 560 this.scope = scope; 561 } 562 563 @Override configure()564 protected void configure() { 565 // ProviderInstanceBinding 566 bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); 567 568 // ProviderKeyBinding 569 bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); 570 571 // LinkedKeyBinding 572 bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); 573 574 // UntargettedBinding / ConstructorBinding 575 bind(FooImpl.class).in(scope); 576 577 // ConstructorBinding 578 bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); 579 } 580 } 581 582 private static class AnnotatedScopeModule extends FooModule { 583 private final Class<? extends Annotation> scope; 584 AnnotatedScopeModule( Class<? extends Annotation> scope, FooImpl foo, Provider<Foo> pFoo, Class<? extends Provider<? extends Foo>> pclFoo, Class<? extends Foo> clFoo, Constructor<FooImpl> cFoo)585 AnnotatedScopeModule( 586 Class<? extends Annotation> scope, 587 FooImpl foo, 588 Provider<Foo> pFoo, 589 Class<? extends Provider<? extends Foo>> pclFoo, 590 Class<? extends Foo> clFoo, 591 Constructor<FooImpl> cFoo) { 592 super(foo, pFoo, pclFoo, clFoo, cFoo); 593 this.scope = scope; 594 } 595 596 @Override configure()597 protected void configure() { 598 // ProviderInstanceBinding 599 bind(Foo.class).annotatedWith(named("pInstance")).toProvider(pFoo).in(scope); 600 601 // ProviderKeyBinding 602 bind(Foo.class).annotatedWith(named("pKey")).toProvider(pclFoo).in(scope); 603 604 // LinkedKeyBinding 605 bind(Foo.class).annotatedWith(named("linkedKey")).to(clFoo).in(scope); 606 607 // UntargettedBinding / ConstructorBinding 608 bind(FooImpl.class).in(scope); 609 610 // ConstructorBinding 611 bind(Foo.class).annotatedWith(named("constructor")).toConstructor(cFoo).in(scope); 612 } 613 } 614 615 private static interface Foo {} 616 617 private static class FooImpl implements Foo { 618 @Inject FooImpl()619 public FooImpl() {} 620 cxtor()621 private static Constructor<FooImpl> cxtor() { 622 try { 623 return FooImpl.class.getConstructor(); 624 } catch (SecurityException e) { 625 throw new RuntimeException(e); 626 } catch (NoSuchMethodException e) { 627 throw new RuntimeException(e); 628 } 629 } 630 } 631 632 private static class FooProvider implements Provider<Foo> { 633 @Override get()634 public Foo get() { 635 return new FooImpl(); 636 } 637 } 638 639 private static class Bar implements Foo { 640 @Inject Bar()641 public Bar() {} 642 cxtor()643 private static Constructor<Bar> cxtor() { 644 try { 645 return Bar.class.getConstructor(); 646 } catch (SecurityException e) { 647 throw new RuntimeException(e); 648 } catch (NoSuchMethodException e) { 649 throw new RuntimeException(e); 650 } 651 } 652 } 653 654 private static class BarProvider implements Provider<Foo> { 655 @Override get()656 public Foo get() { 657 return new Bar(); 658 } 659 } 660 661 private static class HashEqualsTester implements Provider<Object> { 662 private String equality; 663 private boolean throwOnEquals; 664 private boolean throwOnHashcode; 665 666 @Override equals(Object obj)667 public boolean equals(Object obj) { 668 if (throwOnEquals) { 669 throw new RuntimeException(); 670 } else if (obj instanceof HashEqualsTester) { 671 HashEqualsTester o = (HashEqualsTester) obj; 672 if (o.throwOnEquals) { 673 throw new RuntimeException(); 674 } 675 if (equality == null && o.equality == null) { 676 return this == o; 677 } else { 678 return Objects.equal(equality, o.equality); 679 } 680 } else { 681 return false; 682 } 683 } 684 685 @Override hashCode()686 public int hashCode() { 687 if (throwOnHashcode) { 688 throw new RuntimeException(); 689 } else { 690 return super.hashCode(); 691 } 692 } 693 694 @Override get()695 public Object get() { 696 return new Object(); 697 } 698 } 699 } 700