1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @library modules
  27  * @build bananascript/*
  28  * @compile src/pearscript/org/pear/PearScriptEngineFactory.java
  29  *          src/pearscript/org/pear/PearScript.java
  30  * @run testng/othervm Basic
  31  * @summary Basic test for ServiceLoader with a provider deployed as a module.
  32  */
  33 
  34 import java.lang.module.Configuration;
  35 import java.lang.module.ModuleFinder;
  36 import java.lang.reflect.Layer;
  37 import java.nio.file.Files;
  38 import java.nio.file.Path;
  39 import java.nio.file.Paths;
  40 import java.nio.file.StandardCopyOption;
  41 import java.util.*;
  42 import java.util.ServiceLoader.Provider;
  43 import java.util.stream.Collectors;
  44 import javax.script.ScriptEngineFactory;
  45 
  46 import org.testng.annotations.Test;
  47 import org.testng.annotations.BeforeTest;
  48 import static org.testng.Assert.*;
  49 
  50 /**
  51  * Basic test for ServiceLoader. The test make use of two service providers:
  52  * 1. BananaScriptEngine - a ScriptEngineFactory deployed as a module on the
  53  *    module path. It implementations a singleton via the public static
  54  *    provider method.
  55  * 2. PearScriptEngine - a ScriptEngineFactory deployed on the class path
  56  *    with a service configuration file.
  57  */
  58 
  59 public class Basic {
  60 
  61     // Copy the services configuration file for "pearscript" into place.
  62     @BeforeTest
  63     public void setup() throws Exception {
  64         Path src = Paths.get(System.getProperty("test.src", ""));
  65         Path classes = Paths.get(System.getProperty("test.classes", ""));
  66         String st = ScriptEngineFactory.class.getName();
  67         Path config = Paths.get("META-INF", "services", st);
  68         Path source = src.resolve("src").resolve("pearscript").resolve(config);
  69         Path target = classes.resolve(config);
  70         Files.createDirectories(target.getParent());
  71         Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
  72     }
  73 
  74     /**
  75      * Basic test of iterator() to ensure that providers located as modules
  76      * and on the class path are found.
  77      */
  78     @Test
  79     public void testIterator() {
  80         ServiceLoader<ScriptEngineFactory> loader
  81             = ServiceLoader.load(ScriptEngineFactory.class);
  82         Set<String> names = collectAll(loader)
  83                 .stream()
  84                 .map(ScriptEngineFactory::getEngineName)
  85                 .collect(Collectors.toSet());
  86         assertTrue(names.contains("BananaScriptEngine"));
  87         assertTrue(names.contains("PearScriptEngine"));
  88     }
  89 
  90     /**
  91      * Basic test of iterator() to test iteration order. Providers deployed
  92      * as named modules should be found before providers deployed on the class
  93      * path.
  94      */
  95     @Test
  96     public void testIteratorOrder() {
  97         ServiceLoader<ScriptEngineFactory> loader
  98             = ServiceLoader.load(ScriptEngineFactory.class);
  99         boolean foundUnnamed = false;
 100         for (ScriptEngineFactory factory : collectAll(loader)) {
 101             if (factory.getClass().getModule().isNamed()) {
 102                 if (foundUnnamed) {
 103                     assertTrue(false, "Named module element after unnamed");
 104                 }
 105             } else {
 106                 foundUnnamed = true;
 107             }
 108         }
 109     }
 110 
 111     /**
 112      * Basic test of Provider::type
 113      */
 114     @Test
 115     public void testProviderType() {
 116         Set<String> types = ServiceLoader.load(ScriptEngineFactory.class)
 117                 .stream()
 118                 .map(Provider::type)
 119                 .map(Class::getName)
 120                 .collect(Collectors.toSet());
 121         assertTrue(types.contains("org.banana.BananaScriptEngineFactory"));
 122         assertTrue(types.contains("org.pear.PearScriptEngineFactory"));
 123     }
 124 
 125     /**
 126      * Basic test of Provider::get
 127      */
 128     @Test
 129     public void testProviderGet() {
 130         Set<String> names = ServiceLoader.load(ScriptEngineFactory.class)
 131                 .stream()
 132                 .map(Provider::get)
 133                 .map(ScriptEngineFactory::getEngineName)
 134                 .collect(Collectors.toSet());
 135         assertTrue(names.contains("BananaScriptEngine"));
 136         assertTrue(names.contains("PearScriptEngine"));
 137     }
 138 
 139     /**
 140      * Basic test of the public static provider method. BananaScriptEngine
 141      * defines a provider method that returns the same instance.
 142      */
 143     @Test
 144     public void testSingleton() {
 145         Optional<Provider<ScriptEngineFactory>> oprovider
 146             = ServiceLoader.load(ScriptEngineFactory.class)
 147                 .stream()
 148                 .filter(p -> p.type().getName().equals("org.banana.BananaScriptEngineFactory"))
 149                 .findFirst();
 150         assertTrue(oprovider.isPresent());
 151         Provider<ScriptEngineFactory> provider = oprovider.get();
 152 
 153         // invoke Provider::get twice
 154         ScriptEngineFactory factory1 = provider.get();
 155         ScriptEngineFactory factory2 = provider.get();
 156         assertTrue(factory1 == factory2);
 157     }
 158 
 159     /**
 160      * Basic test of stream() to ensure that elements for providers in named
 161      * modules come before elements for providers in unnamed modules.
 162      */
 163     @Test
 164     public void testStreamOrder() {
 165         List<Class<?>> types = ServiceLoader.load(ScriptEngineFactory.class)
 166                 .stream()
 167                 .map(Provider::type)
 168                 .collect(Collectors.toList());
 169 
 170         boolean foundUnnamed = false;
 171         for (Class<?> factoryClass : types) {
 172             if (factoryClass.getModule().isNamed()) {
 173                 if (foundUnnamed) {
 174                     assertTrue(false, "Named module element after unnamed");
 175                 }
 176             } else {
 177                 foundUnnamed = true;
 178             }
 179         }
 180     }
 181 
 182     /**
 183      * Basic test of ServiceLoader.findFirst()
 184      */
 185     @Test
 186     public void testFindFirst() {
 187         Optional<ScriptEngineFactory> ofactory
 188             = ServiceLoader.load(ScriptEngineFactory.class).findFirst();
 189         assertTrue(ofactory.isPresent());
 190         ScriptEngineFactory factory = ofactory.get();
 191         assertTrue(factory.getClass().getModule().isNamed());
 192 
 193         class S { }
 194         assertFalse(ServiceLoader.load(S.class).findFirst().isPresent());
 195     }
 196 
 197     /**
 198      * Basic test ServiceLoader.load specifying the platform class loader.
 199      * The providers on the module path and class path should not be located.
 200      */
 201     @Test
 202     public void testWithPlatformClassLoader() {
 203         ClassLoader pcl = ClassLoader.getPlatformClassLoader();
 204 
 205         // iterator
 206         ServiceLoader<ScriptEngineFactory> loader
 207                 = ServiceLoader.load(ScriptEngineFactory.class, pcl);
 208         Set<String> names = collectAll(loader)
 209                 .stream()
 210                 .map(ScriptEngineFactory::getEngineName)
 211                 .collect(Collectors.toSet());
 212         assertFalse(names.contains("BananaScriptEngine"));
 213         assertFalse(names.contains("PearScriptEngine"));
 214 
 215         // stream
 216         names = ServiceLoader.load(ScriptEngineFactory.class, pcl)
 217                 .stream()
 218                 .map(Provider::get)
 219                 .map(ScriptEngineFactory::getEngineName)
 220                 .collect(Collectors.toSet());
 221         assertFalse(names.contains("BananaScriptEngine"));
 222         assertFalse(names.contains("PearScriptEngine"));
 223     }
 224 
 225     /**
 226      * Basic test of ServiceLoader.load, using the class loader for
 227      * a module in a custom layer as the context.
 228      */
 229     @Test
 230     public void testWithCustomLayer1() {
 231         Layer layer = createCustomLayer("bananascript");
 232 
 233         ClassLoader loader = layer.findLoader("bananascript");
 234         List<ScriptEngineFactory> providers
 235             = collectAll(ServiceLoader.load(ScriptEngineFactory.class, loader));
 236 
 237         // should have at least 2 x bananascript + pearscript
 238         assertTrue(providers.size() >= 3);
 239 
 240         // first element should be the provider in the custom layer
 241         ScriptEngineFactory factory = providers.get(0);
 242         assertTrue(factory.getClass().getClassLoader() == loader);
 243         assertTrue(factory.getClass().getModule().getLayer() == layer);
 244         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 245 
 246         // remainder should be the boot layer
 247         providers.remove(0);
 248         Set<String> names = providers.stream()
 249                 .map(ScriptEngineFactory::getEngineName)
 250                 .collect(Collectors.toSet());
 251         assertTrue(names.contains("BananaScriptEngine"));
 252         assertTrue(names.contains("PearScriptEngine"));
 253     }
 254 
 255     /**
 256      * Basic test of ServiceLoader.load using a custom Layer as the context.
 257      */
 258     @Test
 259     public void testWithCustomLayer2() {
 260         Layer layer = createCustomLayer("bananascript");
 261 
 262         List<ScriptEngineFactory> factories
 263             = collectAll(ServiceLoader.load(layer, ScriptEngineFactory.class));
 264 
 265         // should have at least 2 x bananascript
 266         assertTrue(factories.size() >= 2);
 267 
 268         // first element should be the provider in the custom layer
 269         ScriptEngineFactory factory = factories.get(0);
 270         assertTrue(factory.getClass().getModule().getLayer() == layer);
 271         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 272 
 273         // remainder should be the boot layer
 274         factories.remove(0);
 275         Set<String> names = factories.stream()
 276                 .map(ScriptEngineFactory::getEngineName)
 277                 .collect(Collectors.toSet());
 278         assertTrue(names.contains("BananaScriptEngine"));
 279         assertFalse(names.contains("PearScriptEngine"));
 280     }
 281 
 282     /**
 283      * Basic test of ServiceLoader.load with a tree of layers.
 284      *
 285      * Test scenario:
 286      * - boot layer contains "bananascript", maybe other script engines
 287      * - layer1, with boot layer as parent, contains "bananascript"
 288      * - layer2, with boot layer as parent, contains "bananascript"
 289      * - layer3, with layer1 ad layer as parents, contains "bananascript"
 290      *
 291      * ServiceLoader should locate all 4 script engine factories in DFS order.
 292      */
 293     @Test
 294     public void testWithCustomLayer3() {
 295         Layer bootLayer = Layer.boot();
 296         Configuration cf0 = bootLayer.configuration();
 297 
 298         // boot layer should contain "bananascript"
 299         List<ScriptEngineFactory> factories
 300             = collectAll(ServiceLoader.load(bootLayer, ScriptEngineFactory.class));
 301         int countInBootLayer = factories.size();
 302         assertTrue(countInBootLayer >= 1);
 303         assertTrue(factories.stream()
 304                 .map(p -> p.getEngineName())
 305                 .filter("BananaScriptEngine"::equals)
 306                 .findAny()
 307                 .isPresent());
 308 
 309         ClassLoader scl = ClassLoader.getSystemClassLoader();
 310         Path dir = Paths.get(System.getProperty("test.classes", "."), "modules");
 311         ModuleFinder finder = ModuleFinder.of(dir);
 312 
 313         // layer1
 314         Configuration cf1 = cf0.resolveRequiresAndUses(finder, ModuleFinder.of(), Set.of());
 315         Layer layer1 = bootLayer.defineModulesWithOneLoader(cf1, scl);
 316         assertTrue(layer1.modules().size() == 1);
 317 
 318         // layer2
 319         Configuration cf2 = cf0.resolveRequiresAndUses(finder, ModuleFinder.of(), Set.of());
 320         Layer layer2 = bootLayer.defineModulesWithOneLoader(cf2, scl);
 321         assertTrue(layer2.modules().size() == 1);
 322 
 323         // layer3 with layer1 and layer2 as parents
 324         Configuration cf3 = Configuration.resolveRequiresAndUses(finder,
 325                 List.of(cf1, cf2),
 326                 ModuleFinder.of(),
 327                 Set.of());
 328         Layer layer3 = Layer.defineModulesWithOneLoader(cf3, List.of(layer1, layer2), scl).layer();
 329         assertTrue(layer3.modules().size() == 1);
 330 
 331 
 332         // class loaders
 333         ClassLoader loader1 = layer1.findLoader("bananascript");
 334         ClassLoader loader2 = layer2.findLoader("bananascript");
 335         ClassLoader loader3 = layer3.findLoader("bananascript");
 336         assertTrue(loader1 != loader2);
 337         assertTrue(loader1 != loader3);
 338         assertTrue(loader2 != loader3);
 339 
 340         // load all factories with layer3 as the context
 341         factories = collectAll(ServiceLoader.load(layer3, ScriptEngineFactory.class));
 342         int count = factories.size();
 343         assertTrue(count == countInBootLayer + 3);
 344 
 345         // the ordering should be layer3, layer1, boot layer, layer2
 346 
 347         ScriptEngineFactory factory = factories.get(0);
 348         assertTrue(factory.getClass().getModule().getLayer() == layer3);
 349         assertTrue(factory.getClass().getClassLoader() == loader3);
 350         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 351 
 352         factory = factories.get(1);
 353         assertTrue(factory.getClass().getModule().getLayer() == layer1);
 354         assertTrue(factory.getClass().getClassLoader() == loader1);
 355         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 356 
 357         // boot layer "bananascript" and maybe other factories
 358         int last = count -1;
 359         boolean found = false;
 360         for (int i=2; i<last; i++) {
 361             factory = factories.get(i);
 362             assertTrue(factory.getClass().getModule().getLayer() == bootLayer);
 363             if (factory.getEngineName().equals("BananaScriptEngine")) {
 364                 assertFalse(found);
 365                 found = true;
 366             }
 367         }
 368         assertTrue(found);
 369 
 370         factory = factories.get(last);
 371         assertTrue(factory.getClass().getModule().getLayer() == layer2);
 372         assertTrue(factory.getClass().getClassLoader() == loader2);
 373         assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
 374     }
 375 
 376 
 377     // -- nulls --
 378 
 379     @Test(expectedExceptions = { NullPointerException.class })
 380     public void testLoadNull1() {
 381         ServiceLoader.load(null);
 382     }
 383 
 384     @Test(expectedExceptions = { NullPointerException.class })
 385     public void testLoadNull2() {
 386         ServiceLoader.load((Class<?>) null, ClassLoader.getSystemClassLoader());
 387     }
 388 
 389     @Test(expectedExceptions = { NullPointerException.class })
 390     public void testLoadNull3() {
 391         class S { }
 392         ServiceLoader.load((Layer) null, S.class);
 393     }
 394 
 395     @Test(expectedExceptions = { NullPointerException.class })
 396     public void testLoadNull4() {
 397         ServiceLoader.load(Layer.empty(), null);
 398     }
 399 
 400     @Test(expectedExceptions = { NullPointerException.class })
 401     public void testLoadNull5() {
 402         ServiceLoader.loadInstalled(null);
 403     }
 404 
 405     /**
 406      * Create a custom Layer by resolving the given module names. The modules
 407      * are located in the {@code ${test.classes}/modules} directory.
 408      */
 409     private Layer createCustomLayer(String... modules) {
 410         Path dir = Paths.get(System.getProperty("test.classes", "."), "modules");
 411         ModuleFinder finder = ModuleFinder.of(dir);
 412         Set<String> roots = new HashSet<>();
 413         Collections.addAll(roots, modules);
 414         Layer bootLayer = Layer.boot();
 415         Configuration parent = bootLayer.configuration();
 416         Configuration cf = parent.resolveRequires(finder, ModuleFinder.of(), roots);
 417         ClassLoader scl = ClassLoader.getSystemClassLoader();
 418         Layer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
 419         assertTrue(layer.modules().size() == 1);
 420         return layer;
 421     }
 422 
 423     private <E> List<E> collectAll(ServiceLoader<E> loader) {
 424         List<E> list = new ArrayList<>();
 425         Iterator<E> iterator = loader.iterator();
 426         while (iterator.hasNext()) {
 427             list.add(iterator.next());
 428         }
 429         return list;
 430     }
 431 }
 432