View Javadoc
1   package it.allproducts;
2   
3   import com.atlassian.plugin.spring.scanner.annotation.export.ExportAsService;
4   import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
5   import com.google.common.base.Joiner;
6   import com.google.common.collect.ImmutableList;
7   import org.hamcrest.Description;
8   import org.hamcrest.Matcher;
9   import org.hamcrest.Matchers;
10  import org.hamcrest.TypeSafeMatcher;
11  import org.springframework.stereotype.Component;
12  
13  import java.util.Collection;
14  import java.util.List;
15  import java.util.stream.Collectors;
16  
17  import static java.util.Collections.emptyList;
18  import static org.hamcrest.Matchers.equalTo;
19  
20  /**
21   * Abstracts expected components across {@link TestPackaging} and {@link it.perproduct.AbstractComponentsInProductTest}
22   * subclasses - ensures our packaging tests match our runtime tests, etc.
23   *
24   * The expectations in this class reflect the {@link Component}, {@link ComponentImport}, {@link ExportAsService} etc.
25   * in the test plugin.
26   */
27  public class ComponentExpectations {
28  
29      public static final ComponentExpectations COMMON = new ComponentExpectations();
30  
31      public static final ComponentExpectations REFAPP = new ComponentExpectations(
32              // RefappComponent
33              component("com.atlassian.plugin.spring.scanner.test.product.refapp.RefappOnlyComponent"),
34              // RefappImport
35              componentImport("com.atlassian.refapp.api.ConnectionProvider"),
36              // Extra components that only appear in Refapp
37              ImmutableList.of(autoAddedComponent("org.springframework.context.event.internalEventListenerFactory"),
38                               autoAddedComponent("org.springframework.context.event.internalEventListenerProcessor")));
39  
40      public static final ComponentExpectations CONFLUENCE = new ComponentExpectations(
41              // ConfluenceComponent
42              component("com.atlassian.plugin.spring.scanner.test.product.confluence.ConfluenceOnlyComponent"),
43              // ConfluenceImport
44              componentImport("com.atlassian.confluence.api.service.content.ContentService"),
45              // Extra components that only appear in Confluence
46              ImmutableList.of(autoAddedComponent("org.springframework.context.event.internalEventListenerFactory"),
47                               autoAddedComponent("org.springframework.context.event.internalEventListenerProcessor")));
48  
49      public static final ComponentExpectations JIRA_CLOUD = new ComponentExpectations(
50              // JiraComponent
51              component("com.atlassian.plugin.spring.scanner.test.product.jira.JiraOnlyComponent"),
52              // JiraImport
53              componentImport("com.atlassian.jira.bc.issue.comment.CommentService"),
54              // Extra components that only appear in JIRA Cloud
55              ImmutableList.of(autoAddedComponent("org.springframework.context.event.internalEventListenerFactory"),
56                               autoAddedComponent("org.springframework.context.event.internalEventListenerProcessor")));
57  
58      public static final ComponentExpectations JIRA_BTF = new ComponentExpectations(
59              // JiraComponent
60              component("com.atlassian.plugin.spring.scanner.test.product.jira.JiraOnlyComponent"),
61              // JiraImport
62              componentImport("com.atlassian.jira.bc.issue.comment.CommentService"));
63  
64      public static final ComponentExpectations BAMBOO = new ComponentExpectations(
65              // BambooComponent
66              component("com.atlassian.plugin.spring.scanner.test.product.bamboo.BambooOnlyComponent"),
67              // BambooImport
68              componentImport("com.atlassian.bamboo.build.BuildExecutionManager"));
69  
70      public static final ComponentExpectations STASH = new ComponentExpectations(
71              // StashComponent
72              component("com.atlassian.plugin.spring.scanner.test.product.stash.StashOnlyComponent"),
73              // StashImport
74              componentImport("com.atlassian.stash.repository.RepositoryService"));
75  
76      public static final ComponentExpectations BITBUCKET = new ComponentExpectations(
77              // BitbucketComponent
78              component("com.atlassian.plugin.spring.scanner.test.product.bitbucket.BitbucketOnlyComponent"),
79              // BitbucketImport
80              componentImport("com.atlassian.bitbucket.repository.RepositoryService"));
81  
82      public static final ComponentExpectations FECRU = new ComponentExpectations(
83              // FecruComponent
84              component("com.atlassian.plugin.spring.scanner.test.product.fecru.FecruOnlyComponent"),
85              // FecruImport
86              componentImport("com.atlassian.fisheye.spi.services.RepositoryService"));
87  
88      private final List<ExpectedComponent> productSpecificScannerCreatedComponents;
89      private final List<ExpectedAutoAddedComponent> productSpecificAutoAddedComponents;
90      private final List<ExpectedComponentImport> productSpecificComponentImports;
91  
92      private ComponentExpectations(ExpectedComponent productSpecificComponent,
93                                    ExpectedComponentImport productSpecificComponentImport,
94                                    List<ExpectedAutoAddedComponent> productSpecificAutoAddedComponents) {
95          this.productSpecificScannerCreatedComponents = ImmutableList.of(productSpecificComponent);
96          this.productSpecificComponentImports = ImmutableList.of(productSpecificComponentImport);
97          this.productSpecificAutoAddedComponents = productSpecificAutoAddedComponents;
98      }
99  
100     private ComponentExpectations(ExpectedComponent productSpecificComponent,
101                                   ExpectedComponentImport productSpecificComponentImport) {
102         this(productSpecificComponent, productSpecificComponentImport, emptyList());
103     }
104 
105     private ComponentExpectations() {
106         this.productSpecificScannerCreatedComponents = emptyList();
107         this.productSpecificComponentImports = emptyList();
108         this.productSpecificAutoAddedComponents = emptyList();
109     }
110 
111     /**
112      * Things in META-INF/plugin-components/components
113      */
114     public List<ExpectedComponent> getExpectedScannerCreatedComponents() {
115         return ImmutableList.<ExpectedComponent>builder()
116                 // Test plugin's components instantiated in different ways
117                 .add(component("com.atlassian.plugin.spring.scanner.test.ConsumingInternalOnlyComponent"))
118                 .add(component("com.atlassian.plugin.spring.scanner.test.imported.ConsumingMixedComponents"))
119                 .add(component("com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponent"))
120                 .add(component("com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentWithSpecifiedInterface"))
121                 .add(component("com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentWithMultipleSpecifiedInterfaces"))
122                 .add(component("com.atlassian.plugin.spring.scanner.test.exported.ExposedAsADevServiceComponent"))
123                 .add(component("com.atlassian.plugin.spring.scanner.test.exported.ExportExternalComponentsViaFields$ExternalServiceViaField"))
124                 .add(component("com.atlassian.plugin.spring.scanner.test.exported.ExportExternalComponentsViaFields$ExternalDevServiceViaField"))
125                 .add(component("com.atlassian.plugin.spring.scanner.test.InternalComponent"))
126                 .add(component("com.atlassian.plugin.spring.scanner.test.InternalComponentTwo"))
127                 .add(component("com.atlassian.plugin.spring.scanner.test.NamedComponent", "namedComponent"))
128                 .add(component("com.atlassian.plugin.spring.scanner.test.imported.NamedConsumingMixedComponents", "namedMixed"))
129                 .add(component("com.atlassian.plugin.spring.scanner.test.OuterClass$InnerClass$InnerComponent$EvenMoreInnerComponent"))
130                 .add(component("com.atlassian.plugin.spring.scanner.test.OuterClass$InnerClass$InnerComponent"))
131                 .add(component("com.atlassian.plugin.spring.scanner.test.OuterClass$OuterComponent"))
132                 .add(component("com.atlassian.plugin.spring.scanner.test.primary.BaseImplementation"))
133                 .add(component("com.atlassian.plugin.spring.scanner.test.primary.PrimaryImplementation"))
134                 .add(component("com.atlassian.plugin.spring.scanner.test.primary.ConsumingPrimaryComponentByInterface"))
135 
136                 .add(component("com.atlassian.plugin.spring.scanner.test.fields.ComponentWithFields"))
137 
138                 .add(component("com.atlassian.plugin.spring.scanner.test.spring.ControllerComponent"))
139                 .add(component("com.atlassian.plugin.spring.scanner.test.spring.RepositoryComponent"))
140                 .add(component("com.atlassian.plugin.spring.scanner.test.spring.ServiceComponent"))
141 
142                 .add(component("com.atlassian.plugin.spring.scanner.test.jsr.JsrComponent"))
143 
144                 .add(component("com.atlassian.plugin.spring.scanner.external.component.ExternalJarComponentOne"))
145                 .add(component("com.atlassian.plugin.spring.scanner.external.component.ExternalJarComponentTwo"))
146                 .add(component("com.atlassian.plugin.spring.scanner.external.component.ExternalJarComponentComposite"))
147                 .add(component("com.atlassian.plugin.spring.scanner.external.notannotated.NotAnnotatedExternalJarClass"))
148 
149                 .add(component("com.atlassian.plugin.spring.scanner.external.component.TransitiveJarComponent1"))
150                 .add(component("com.atlassian.plugin.spring.scanner.external.component.TransitiveJarComponent2"))
151                 .add(component("com.atlassian.plugin.spring.scanner.external.component.TransitiveJarComponentComposite"))
152 
153                 .add(component("com.atlassian.plugin.spring.scanner.test.imported.ConsumesExternalComponentsViaConstructor"))
154                 .add(component("com.atlassian.plugin.spring.scanner.test.imported.ConsumesExternalComponentsViaFields"))
155 
156                 // Test plugin's components - test infrastructure
157                 .add(component("com.atlassian.plugin.spring.scanner.test.dynamic.DynamicContextManager"))
158                 .add(component("com.atlassian.plugin.spring.scanner.test.registry.BeanLister"))
159 
160                 // Module type
161                 .add(component("com.atlassian.plugin.spring.scanner.test.moduletype.BasicModuleTypeFactory"))
162                 // because we have a @ModuleType, we get an extra component in support of that
163                 .add(component("com.atlassian.plugin.osgi.bridge.external.SpringHostContainer"))
164                 .build();
165     }
166 
167     /**
168      * Common components instantiated by scanner programmatically: don't appear in META-INF/plugin-components
169      */
170     public List<ExpectedAutoAddedComponent> getExpectedAutoAddedComponents() {
171         return ImmutableList.<ExpectedAutoAddedComponent>builder()
172                 // Scanner's internal components auto-added to spring context
173                 .add(autoAddedComponent("devModeBeanInitialisationLoggerBeanPostProcessor", "com.atlassian.plugin.spring.scanner.runtime.impl.DevModeBeanInitialisationLoggerBeanPostProcessor"))
174                 .add(autoAddedComponent("serviceExportBeanPostProcessor", "com.atlassian.plugin.spring.scanner.runtime.impl.ServiceExporterBeanPostProcessor"))
175                 .add(autoAddedComponent("componentImportBeanFactoryPostProcessor", "com.atlassian.plugin.spring.scanner.runtime.impl.ComponentImportBeanFactoryPostProcessor"))
176 
177                 // Spring internal components
178                 .add(autoAddedComponent("org.springframework.context.annotation.internalAutowiredAnnotationProcessor", "org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"))
179                 .add(autoAddedComponent("org.springframework.context.annotation.internalCommonAnnotationProcessor", "org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"))
180                 .add(autoAddedComponent("org.springframework.context.annotation.internalConfigurationAnnotationProcessor", "org.springframework.context.annotation.ConfigurationClassPostProcessor"))
181                 .add(autoAddedComponent("org.springframework.context.annotation.internalRequiredAnnotationProcessor", "org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"))
182                 .build();
183     }
184 
185     /**
186      * Things in META-INF/plugin-components/imports
187      */
188     public List<ExpectedComponentImport> getExpectedScannerCreatedComponentImports() {
189         return ImmutableList.<ExpectedComponentImport>builder()
190                 .add(componentImport("com.atlassian.event.api.EventPublisher"))
191                 .add(componentImport("com.atlassian.plugin.PluginAccessor"))
192                 .add(componentImport("com.atlassian.plugin.module.ModuleFactory"))
193                 .add(componentImport("com.atlassian.plugin.spring.scanner.test.otherplugin.ServiceExportedFromAnotherPlugin"))
194                 // .add(componentImport("ServiceTwoExportedFromAnotherPlugin")) is only in dynamic profile
195                 .add(componentImport("com.atlassian.plugin.spring.scanner.test.otherplugin.ServiceTwoExportedFromAnotherPlugin"))
196                 .build();
197     }
198 
199     /**
200      * Things in META-INF/plugin-components/exports
201      */
202     public List<ExpectedServiceExport> getExpectedScannerCreatedExports() {
203         return ImmutableList.<ExpectedServiceExport>builder()
204                 // Explicitly exported services
205                 .add(serviceExport(
206                         "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponent",
207                         ImmutableList.of(
208                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterface")))
209                 .add(serviceExport(
210                         "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentWithSpecifiedInterface",
211                         ImmutableList.of(
212                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterfaceThree"),
213                         ImmutableList.of(
214                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterfaceThree")))
215                 .add(serviceExport(
216                         "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentWithMultipleSpecifiedInterfaces",
217                         ImmutableList.of(
218                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterface",
219                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterfaceTwo"),
220                         ImmutableList.of(
221                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterface",
222                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsAServiceComponentInterfaceTwo")))
223                 .add(serviceExport(
224                         "com.atlassian.plugin.spring.scanner.test.exported.ExportExternalComponentsViaFields$ExternalServiceViaField",
225                         ImmutableList.of(
226                                 "com.atlassian.plugin.spring.scanner.test.exported.ExportExternalComponentsViaFields$ExternalServiceViaField")))
227 
228                 // Module type
229                 .add(serviceExport("com.atlassian.plugin.spring.scanner.test.moduletype.BasicModuleTypeFactory",
230                         ImmutableList.of(
231                                 "com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory"),
232                         ImmutableList.of(
233                                 "com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory")))
234                 .build();
235     }
236 
237     /**
238      * Things in META-INF/plugin-components/dev-exports
239      */
240     public List<ExpectedServiceExport> getExpectedScannerCreatedDevExports() {
241         return ImmutableList.of(
242                 serviceExport(
243                         "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsADevServiceComponent",
244                         ImmutableList.of(
245                                 "com.atlassian.plugin.spring.scanner.test.exported.ExposedAsADevServiceComponent")),
246                 serviceExport(
247                         "com.atlassian.plugin.spring.scanner.test.exported.ExportExternalComponentsViaFields$ExternalDevServiceViaField",
248                         ImmutableList.of(
249                                 "com.atlassian.plugin.spring.scanner.test.exported.ExportExternalComponentsViaFields$ExternalDevServiceViaField")));
250     }
251 
252     /**
253      * Common stuff made by scanner programmatically: don't appear in META-INF/plugin-components
254      */
255     public List<ExpectedServiceExport> getExpectedAutoAddedExports() {
256         return ImmutableList.of(
257                 // Gemini's common services
258                 serviceExport(null,
259                         ImmutableList.<String>builder()
260                             .add("org.eclipse.gemini.blueprint.context.DelegatedExecutionOsgiBundleApplicationContext")
261                             .add("org.eclipse.gemini.blueprint.context.ConfigurableOsgiBundleApplicationContext")
262                             .add("org.springframework.context.ConfigurableApplicationContext")
263                             .add("org.springframework.context.ApplicationContext")
264                             .add("org.springframework.context.Lifecycle")
265                             .add("java.io.Closeable")
266                             .add("org.springframework.beans.factory.ListableBeanFactory")
267                             .add("org.springframework.beans.factory.HierarchicalBeanFactory")
268                             .add("org.springframework.context.MessageSource")
269                             .add("org.springframework.context.ApplicationEventPublisher")
270                             .add("org.springframework.core.io.support.ResourcePatternResolver")
271                             .add("org.springframework.beans.factory.BeanFactory")
272                             .add("org.springframework.core.io.ResourceLoader")
273                             .add("java.lang.AutoCloseable")
274                             .add("org.springframework.beans.factory.DisposableBean")
275                             .build()));
276     }
277 
278     /**
279      * Things in META-INF/plugin-components/profile-dynamic/component
280      */
281     public List<ExpectedComponent> getExpectedScannerCreatedDynamicComponents() {
282         return ImmutableList.of(component("com.atlassian.plugin.spring.scanner.test.dynamic.DynamicComponent"));
283     }
284 
285     /**
286      * Things in META-INF/plugin-components/profile-dynamic/imports
287      */
288     public List<ExpectedComponentImport> getExpectedScannerCreatedDynamicComponentImports() {
289         return ImmutableList.of(
290                 componentImport("com.atlassian.plugin.spring.scanner.test.otherplugin.DynamicallyImportedServiceFromAnotherPlugin"));
291     }
292 
293     /**
294      * Things in META-INF/plugin-components/component-{product}
295      */
296     public List<ExpectedComponent> getProductSpecificScannerCreatedComponents() {
297         return productSpecificScannerCreatedComponents;
298     }
299 
300     /**
301      * Things in META-INF/plugin-components/imports-{product}
302      */
303     public List<ExpectedComponentImport> getProductSpecificScannerCreatedComponentImports() {
304         return productSpecificComponentImports;
305     }
306 
307     /**
308      * Product-specific stuff made by product programmatically: don't appear in META-INF/component-{product}
309      */
310     public List<ExpectedAutoAddedComponent> getProductSpecificAutoAddedComponents() {
311         return productSpecificAutoAddedComponents;
312     }
313 
314     private static String toComponentName(String classNameWithPackage) {
315         final String simpleClassName = classNameWithPackage.replaceAll(".*\\.", "").replace('$', '.');
316         return simpleClassName.substring(0, 1).toLowerCase() + simpleClassName.substring(1);
317     }
318 
319     private static ExpectedComponent component(String implClass) {
320         return new ExpectedComponent(implClass, null);
321     }
322 
323     private static ExpectedComponent component(String implClass, String specifiedName) {
324         return new ExpectedComponent(implClass, specifiedName);
325     }
326 
327     private static ExpectedComponentImport componentImport(String iface) {
328         return new ExpectedComponentImport(iface);
329     }
330 
331     private static ExpectedAutoAddedComponent autoAddedComponent(String name) {
332         return new ExpectedAutoAddedComponent(name);
333     }
334 
335     private static ExpectedAutoAddedComponent autoAddedComponent(String name, String implClass) {
336         return new ExpectedAutoAddedComponent(name, implClass);
337     }
338 
339     private static ExpectedServiceExport serviceExport(String implClass, List<String> expectedExportedInterfaces) {
340         return new ExpectedServiceExport(implClass, expectedExportedInterfaces, null);
341     }
342 
343     private static ExpectedServiceExport serviceExport(String implClass, List<String> expectedExportedInterfaces, List<String> specifiedInterfaces) {
344         return new ExpectedServiceExport(implClass, expectedExportedInterfaces, specifiedInterfaces);
345     }
346 
347     public static abstract class AbstractExpectedComponent {
348         /**
349          * What do we expect to appear in META-INF/plugin-components index file lines?
350          *
351          * @see TestPackaging
352          */
353         public abstract Matcher<String> getIndexLineMatcher();
354 
355         /**
356          * What do we expect to appear as a runtime component
357          * as output by {@link com.atlassian.plugin.spring.scanner.test.servlet.ComponentStatusServlet}?
358          *
359          * @see it.perproduct.AbstractComponentsInProductTest
360          */
361         public abstract Matcher<String> getRuntimeComponentEntryMatcher();
362     }
363 
364     public static class ExpectedComponent extends AbstractExpectedComponent {
365         private final String implClass;
366         private final String specifiedName;
367 
368         public ExpectedComponent(String implClass, String specifiedName) {
369             this.implClass = implClass;
370             this.specifiedName = specifiedName;
371         }
372 
373         @Override
374         public Matcher<String> getIndexLineMatcher() {
375             return equalTo(implClass + ((specifiedName == null) ? "" : "#" + specifiedName));
376         }
377 
378         @Override
379         public Matcher<String> getRuntimeComponentEntryMatcher() {
380             return equalTo(((specifiedName != null) ? specifiedName : toComponentName(implClass))
381                     + " = " + implClass);
382         }
383     }
384 
385     public static class ExpectedComponentImport extends AbstractExpectedComponent {
386         private final String iface;
387 
388         private ExpectedComponentImport(String iface) {
389             this.iface = iface;
390         }
391 
392         @Override
393         public Matcher<String> getIndexLineMatcher() {
394             return equalTo(iface);
395         }
396 
397         @Override
398         public Matcher<String> getRuntimeComponentEntryMatcher() {
399             // Don't care much about actual imported impl class - some of them vary by product
400             return new AnyImplClassComponentMatcher(toComponentName(iface));
401         }
402     }
403 
404     public static class ExpectedAutoAddedComponent extends AbstractExpectedComponent {
405         private final String name;
406         private final String implClass;
407 
408         private ExpectedAutoAddedComponent(String name) {
409             this.name = name;
410             this.implClass = null;
411         }
412 
413         private ExpectedAutoAddedComponent(String name, String implClass) {
414             this.name = name;
415             this.implClass = implClass;
416         }
417 
418         @Override
419         public Matcher<String> getIndexLineMatcher() {
420             // Created at runtime, doesn't appear in index files
421             return null;
422         }
423 
424         @Override
425         public Matcher<String> getRuntimeComponentEntryMatcher() {
426             if (implClass == null) {
427                 // Don't care much about actual imported impl class - some of them vary by product
428                 return new AnyImplClassComponentMatcher(name);
429             }
430             return equalTo(name + " = " + implClass);
431         }
432     }
433 
434     public static class ExpectedServiceExport extends AbstractExpectedComponent {
435         private final String implClass;
436         private final List<String> expectedExportedInterfaces;
437         private final List<String> specifiedInterfaces;
438 
439         private ExpectedServiceExport(String implClass,
440                                       List<String> expectedExportedInterfaces,
441                                       List<String> specifiedInterfaces) {
442             this.implClass = implClass;
443             this.expectedExportedInterfaces = expectedExportedInterfaces;
444             this.specifiedInterfaces = specifiedInterfaces;
445         }
446 
447         @Override
448         public Matcher<String> getIndexLineMatcher() {
449             return equalTo(implClass +
450                     ((specifiedInterfaces != null)
451                             ? ("#" + Joiner.on(",").join(specifiedInterfaces))
452                             : ""));
453         }
454 
455         @Override
456         public Matcher<String> getRuntimeComponentEntryMatcher() {
457             if (expectedExportedInterfaces == null) {
458                 return equalTo(implClass);
459             }
460             return new InterfaceMatcherInAnyOrder(expectedExportedInterfaces);
461         }
462     }
463 
464     private static class InterfaceMatcherInAnyOrder extends TypeSafeMatcher<String> {
465         private final List<String> interfaces;
466 
467         public InterfaceMatcherInAnyOrder(List<String> interfaces) {
468             this.interfaces = interfaces;
469         }
470 
471         @Override
472         protected boolean matchesSafely(String item) {
473             // grr hamcrest generics
474             Collection<Matcher<? super String>> expected = interfaces.stream()
475                     .map(Matchers::equalTo)
476                     .collect(Collectors.toList());
477             Iterable<? extends String> actual = ImmutableList.<String>builder().add(item.split(",")).build();
478             final Matcher<Iterable<? extends String>> iterableMatcher
479                     = Matchers.containsInAnyOrder(expected);
480             return iterableMatcher.matches(actual);
481         }
482 
483         @Override
484         public void describeTo(Description description) {
485             description.appendText("exported interfaces: '" + interfaces + "'");
486         }
487     }
488 
489     /**
490      * Match expected components by name only, coping with implementation classes that vary between products.
491      */
492     private static class AnyImplClassComponentMatcher extends TypeSafeMatcher<String> {
493 
494         private final String componentName;
495 
496         public AnyImplClassComponentMatcher(String componentName) {
497             this.componentName = componentName;
498         }
499 
500         @Override
501         protected boolean matchesSafely(String item) {
502             // Wildcards for cross-product variability in impl classes: e.g. matches both:
503             //   eventPublisher = com.atlassian.event.internal.EventPublisherImpl
504             //   eventPublisher = com.atlassian.confluence.event.TimingEventPublisher
505             return item.startsWith(componentName + " = ");
506         }
507 
508         @Override
509         public void describeTo(Description description) {
510             description.appendText("component named: '" + componentName + "' (any impl)");
511         }
512     }
513 }