Home | History | Annotate | Download | only in spi
      1 /*
      2  * Copyright (C) 2015 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.spi;
     18 
     19 import static com.google.inject.Asserts.assertContains;
     20 import static com.google.inject.name.Names.named;
     21 import static java.lang.annotation.ElementType.METHOD;
     22 import static java.lang.annotation.RetentionPolicy.RUNTIME;
     23 
     24 import com.google.common.collect.ImmutableSet;
     25 import com.google.common.collect.Iterables;
     26 import com.google.inject.AbstractModule;
     27 import com.google.inject.Binder;
     28 import com.google.inject.Binding;
     29 import com.google.inject.CreationException;
     30 import com.google.inject.Exposed;
     31 import com.google.inject.Guice;
     32 import com.google.inject.Injector;
     33 import com.google.inject.Key;
     34 import com.google.inject.Module;
     35 import com.google.inject.PrivateModule;
     36 import com.google.inject.internal.util.StackTraceElements;
     37 import com.google.inject.name.Named;
     38 import com.google.inject.name.Names;
     39 import java.lang.annotation.Annotation;
     40 import java.lang.annotation.Documented;
     41 import java.lang.annotation.Retention;
     42 import java.lang.annotation.Target;
     43 import java.util.Set;
     44 import junit.framework.TestCase;
     45 
     46 /** Tests for {@link ModuleAnnotatedMethodScanner} usage. */
     47 public class ModuleAnnotatedMethodScannerTest extends TestCase {
     48 
     49   public void testScanning() throws Exception {
     50     Module module =
     51         new AbstractModule() {
     52 
     53           @TestProvides
     54           @Named("foo")
     55           String foo() {
     56             return "foo";
     57           }
     58 
     59           @TestProvides
     60           @Named("foo2")
     61           String foo2() {
     62             return "foo2";
     63           }
     64         };
     65     Injector injector = Guice.createInjector(module, NamedMunger.module());
     66 
     67     // assert no bindings named "foo" or "foo2" exist -- they were munged.
     68     assertMungedBinding(injector, String.class, "foo", "foo");
     69     assertMungedBinding(injector, String.class, "foo2", "foo2");
     70 
     71     Binding<String> fooBinding = injector.getBinding(Key.get(String.class, named("foo-munged")));
     72     Binding<String> foo2Binding = injector.getBinding(Key.get(String.class, named("foo2-munged")));
     73     // Validate the provider has a sane toString
     74     assertEquals(
     75         methodName(TestProvides.class, "foo", module), fooBinding.getProvider().toString());
     76     assertEquals(
     77         methodName(TestProvides.class, "foo2", module), foo2Binding.getProvider().toString());
     78   }
     79 
     80   public void testSkipSources() throws Exception {
     81     Module module =
     82         new AbstractModule() {
     83           @Override
     84           protected void configure() {
     85             binder()
     86                 .skipSources(getClass())
     87                 .install(
     88                     new AbstractModule() {
     89 
     90                       @TestProvides
     91                       @Named("foo")
     92                       String foo() {
     93                         return "foo";
     94                       }
     95                     });
     96           }
     97         };
     98     Injector injector = Guice.createInjector(module, NamedMunger.module());
     99     assertMungedBinding(injector, String.class, "foo", "foo");
    100   }
    101 
    102   public void testWithSource() throws Exception {
    103     Module module =
    104         new AbstractModule() {
    105           @Override
    106           protected void configure() {
    107             binder()
    108                 .withSource("source")
    109                 .install(
    110                     new AbstractModule() {
    111 
    112                       @TestProvides
    113                       @Named("foo")
    114                       String foo() {
    115                         return "foo";
    116                       }
    117                     });
    118           }
    119         };
    120     Injector injector = Guice.createInjector(module, NamedMunger.module());
    121     assertMungedBinding(injector, String.class, "foo", "foo");
    122   }
    123 
    124   public void testMoreThanOneClaimedAnnotationFails() throws Exception {
    125     Module module =
    126         new AbstractModule() {
    127 
    128           @TestProvides
    129           @TestProvides2
    130           String foo() {
    131             return "foo";
    132           }
    133         };
    134     try {
    135       Guice.createInjector(module, NamedMunger.module());
    136       fail();
    137     } catch (CreationException expected) {
    138       assertEquals(1, expected.getErrorMessages().size());
    139       assertContains(
    140           expected.getMessage(),
    141           "More than one annotation claimed by NamedMunger on method "
    142               + module.getClass().getName()
    143               + ".foo(). Methods can only have "
    144               + "one annotation claimed per scanner.");
    145     }
    146   }
    147 
    148   private String methodName(Class<? extends Annotation> annotation, String method, Object container)
    149       throws Exception {
    150     return "@"
    151         + annotation.getName()
    152         + " "
    153         + StackTraceElements.forMember(container.getClass().getDeclaredMethod(method));
    154   }
    155 
    156   @Documented
    157   @Target(METHOD)
    158   @Retention(RUNTIME)
    159   private @interface TestProvides {}
    160 
    161   @Documented
    162   @Target(METHOD)
    163   @Retention(RUNTIME)
    164   private @interface TestProvides2 {}
    165 
    166   private static class NamedMunger extends ModuleAnnotatedMethodScanner {
    167     static Module module() {
    168       return new AbstractModule() {
    169         @Override
    170         protected void configure() {
    171           binder().scanModulesForAnnotatedMethods(new NamedMunger());
    172         }
    173       };
    174     }
    175 
    176     @Override
    177     public String toString() {
    178       return "NamedMunger";
    179     }
    180 
    181     @Override
    182     public Set<? extends Class<? extends Annotation>> annotationClasses() {
    183       return ImmutableSet.of(TestProvides.class, TestProvides2.class);
    184     }
    185 
    186     @Override
    187     public <T> Key<T> prepareMethod(
    188         Binder binder, Annotation annotation, Key<T> key, InjectionPoint injectionPoint) {
    189       return Key.get(
    190           key.getTypeLiteral(), Names.named(((Named) key.getAnnotation()).value() + "-munged"));
    191     }
    192   }
    193 
    194   private void assertMungedBinding(
    195       Injector injector, Class<?> clazz, String originalName, Object expectedValue) {
    196     assertNull(injector.getExistingBinding(Key.get(clazz, named(originalName))));
    197     Binding<?> fooBinding = injector.getBinding(Key.get(clazz, named(originalName + "-munged")));
    198     assertEquals(expectedValue, fooBinding.getProvider().get());
    199   }
    200 
    201   public void testFailingScanner() {
    202     try {
    203       Guice.createInjector(new SomeModule(), FailingScanner.module());
    204       fail();
    205     } catch (CreationException expected) {
    206       Message m = Iterables.getOnlyElement(expected.getErrorMessages());
    207       assertEquals(
    208           "An exception was caught and reported. Message: Failing in the scanner.", m.getMessage());
    209       assertEquals(IllegalStateException.class, m.getCause().getClass());
    210       ElementSource source = (ElementSource) Iterables.getOnlyElement(m.getSources());
    211       assertEquals(
    212           SomeModule.class.getName(), Iterables.getOnlyElement(source.getModuleClassNames()));
    213       assertEquals(
    214           String.class.getName() + " " + SomeModule.class.getName() + ".aString()",
    215           source.toString());
    216     }
    217   }
    218 
    219   public static class FailingScanner extends ModuleAnnotatedMethodScanner {
    220     static Module module() {
    221       return new AbstractModule() {
    222         @Override
    223         protected void configure() {
    224           binder().scanModulesForAnnotatedMethods(new FailingScanner());
    225         }
    226       };
    227     }
    228 
    229     @Override
    230     public Set<? extends Class<? extends Annotation>> annotationClasses() {
    231       return ImmutableSet.of(TestProvides.class);
    232     }
    233 
    234     @Override
    235     public <T> Key<T> prepareMethod(
    236         Binder binder, Annotation rawAnnotation, Key<T> key, InjectionPoint injectionPoint) {
    237       throw new IllegalStateException("Failing in the scanner.");
    238     }
    239   }
    240 
    241   static class SomeModule extends AbstractModule {
    242     @TestProvides
    243     String aString() {
    244       return "Foo";
    245     }
    246 
    247   }
    248 
    249   public void testChildInjectorInheritsScanner() {
    250     Injector parent = Guice.createInjector(NamedMunger.module());
    251     Injector child =
    252         parent.createChildInjector(
    253             new AbstractModule() {
    254 
    255               @TestProvides
    256               @Named("foo")
    257               String foo() {
    258                 return "foo";
    259               }
    260             });
    261     assertMungedBinding(child, String.class, "foo", "foo");
    262   }
    263 
    264   public void testChildInjectorScannersDontImpactSiblings() {
    265     Module module =
    266         new AbstractModule() {
    267 
    268           @TestProvides
    269           @Named("foo")
    270           String foo() {
    271             return "foo";
    272           }
    273         };
    274     Injector parent = Guice.createInjector();
    275     Injector child = parent.createChildInjector(NamedMunger.module(), module);
    276     assertMungedBinding(child, String.class, "foo", "foo");
    277 
    278     // no foo nor foo-munged in sibling, since scanner never saw it.
    279     Injector sibling = parent.createChildInjector(module);
    280     assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo"))));
    281     assertNull(sibling.getExistingBinding(Key.get(String.class, named("foo-munged"))));
    282   }
    283 
    284   public void testPrivateModuleInheritScanner_usingPrivateModule() {
    285     Injector injector =
    286         Guice.createInjector(
    287             NamedMunger.module(),
    288             new PrivateModule() {
    289               @Override
    290               protected void configure() {}
    291 
    292               @Exposed
    293               @TestProvides
    294               @Named("foo")
    295               String foo() {
    296                 return "foo";
    297               }
    298             });
    299     assertMungedBinding(injector, String.class, "foo", "foo");
    300   }
    301 
    302   public void testPrivateModule_skipSourcesWithinPrivateModule() {
    303     Injector injector =
    304         Guice.createInjector(
    305             NamedMunger.module(),
    306             new PrivateModule() {
    307               @Override
    308               protected void configure() {
    309                 binder()
    310                     .skipSources(getClass())
    311                     .install(
    312                         new AbstractModule() {
    313 
    314                           @Exposed
    315                           @TestProvides
    316                           @Named("foo")
    317                           String foo() {
    318                             return "foo";
    319                           }
    320                         });
    321               }
    322             });
    323     assertMungedBinding(injector, String.class, "foo", "foo");
    324   }
    325 
    326   public void testPrivateModule_skipSourcesForPrivateModule() {
    327     Injector injector =
    328         Guice.createInjector(
    329             NamedMunger.module(),
    330             new AbstractModule() {
    331               @Override
    332               protected void configure() {
    333                 binder()
    334                     .skipSources(getClass())
    335                     .install(
    336                         new PrivateModule() {
    337                           @Override
    338                           protected void configure() {}
    339 
    340                           @Exposed
    341                           @TestProvides
    342                           @Named("foo")
    343                           String foo() {
    344                             return "foo";
    345                           }
    346                         });
    347               }
    348             });
    349     assertMungedBinding(injector, String.class, "foo", "foo");
    350   }
    351 
    352   public void testPrivateModuleInheritScanner_usingPrivateBinder() {
    353     Injector injector =
    354         Guice.createInjector(
    355             NamedMunger.module(),
    356             new AbstractModule() {
    357               @Override
    358               protected void configure() {
    359                 binder()
    360                     .newPrivateBinder()
    361                     .install(
    362                         new AbstractModule() {
    363 
    364                           @Exposed
    365                           @TestProvides
    366                           @Named("foo")
    367                           String foo() {
    368                             return "foo";
    369                           }
    370                         });
    371               }
    372             });
    373     assertMungedBinding(injector, String.class, "foo", "foo");
    374   }
    375 
    376   public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder() {
    377     Injector injector =
    378         Guice.createInjector(
    379             NamedMunger.module(),
    380             new AbstractModule() {
    381               @Override
    382               protected void configure() {
    383                 binder()
    384                     .newPrivateBinder()
    385                     .skipSources(getClass())
    386                     .install(
    387                         new AbstractModule() {
    388 
    389                           @Exposed
    390                           @TestProvides
    391                           @Named("foo")
    392                           String foo() {
    393                             return "foo";
    394                           }
    395                         });
    396               }
    397             });
    398     assertMungedBinding(injector, String.class, "foo", "foo");
    399   }
    400 
    401   public void testPrivateModuleInheritScanner_skipSourcesFromPrivateBinder2() {
    402     Injector injector =
    403         Guice.createInjector(
    404             NamedMunger.module(),
    405             new AbstractModule() {
    406               @Override
    407               protected void configure() {
    408                 binder()
    409                     .skipSources(getClass())
    410                     .newPrivateBinder()
    411                     .install(
    412                         new AbstractModule() {
    413 
    414                           @Exposed
    415                           @TestProvides
    416                           @Named("foo")
    417                           String foo() {
    418                             return "foo";
    419                           }
    420                         });
    421               }
    422             });
    423     assertMungedBinding(injector, String.class, "foo", "foo");
    424   }
    425 
    426   public void testPrivateModuleScannersDontImpactSiblings_usingPrivateModule() {
    427     Injector injector =
    428         Guice.createInjector(
    429             new PrivateModule() {
    430               @Override
    431               protected void configure() {
    432                 install(NamedMunger.module());
    433               }
    434 
    435               @Exposed
    436               @TestProvides
    437               @Named("foo")
    438               String foo() {
    439                 return "foo";
    440               }
    441             },
    442             new PrivateModule() {
    443               @Override
    444               protected void configure() {}
    445 
    446               // ignored! (because the scanner doesn't run over this module)
    447               @Exposed
    448               @TestProvides
    449               @Named("foo")
    450               String foo() {
    451                 return "foo";
    452               }
    453             });
    454     assertMungedBinding(injector, String.class, "foo", "foo");
    455   }
    456 
    457   public void testPrivateModuleScannersDontImpactSiblings_usingPrivateBinder() {
    458     Injector injector =
    459         Guice.createInjector(
    460             new AbstractModule() {
    461               @Override
    462               protected void configure() {
    463                 binder()
    464                     .newPrivateBinder()
    465                     .install(
    466                         new AbstractModule() {
    467                           @Override
    468                           protected void configure() {
    469                             install(NamedMunger.module());
    470                           }
    471 
    472                           @Exposed
    473                           @TestProvides
    474                           @Named("foo")
    475                           String foo() {
    476                             return "foo";
    477                           }
    478                         });
    479               }
    480             },
    481             new AbstractModule() {
    482               @Override
    483               protected void configure() {
    484                 binder()
    485                     .newPrivateBinder()
    486                     .install(
    487                         new AbstractModule() {
    488 
    489                           // ignored! (because the scanner doesn't run over this module)
    490                           @Exposed
    491                           @TestProvides
    492                           @Named("foo")
    493                           String foo() {
    494                             return "foo";
    495                           }
    496                         });
    497               }
    498             });
    499     assertMungedBinding(injector, String.class, "foo", "foo");
    500   }
    501 
    502   public void testPrivateModuleWithinPrivateModule() {
    503     Injector injector =
    504         Guice.createInjector(
    505             NamedMunger.module(),
    506             new PrivateModule() {
    507               @Override
    508               protected void configure() {
    509                 expose(Key.get(String.class, named("foo-munged")));
    510                 install(
    511                     new PrivateModule() {
    512                       @Override
    513                       protected void configure() {}
    514 
    515                       @Exposed
    516                       @TestProvides
    517                       @Named("foo")
    518                       String foo() {
    519                         return "foo";
    520                       }
    521                     });
    522               }
    523             });
    524     assertMungedBinding(injector, String.class, "foo", "foo");
    525   }
    526 }
    527