1/*
2 * Copyright (c) 2016, 2017, 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 * @modules java.scripting
27 * @library modules /lib/testlibrary
28 * @build bananascript/*
29 * @build JarUtils
30 * @compile classpath/pearscript/org/pear/PearScriptEngineFactory.java
31 *          classpath/pearscript/org/pear/PearScript.java
32 * @run testng/othervm ModulesTest
33 * @summary Basic test for ServiceLoader with a provider deployed as a module.
34 */
35
36import java.lang.module.Configuration;
37import java.lang.module.ModuleFinder;
38import java.nio.file.Files;
39import java.nio.file.Path;
40import java.nio.file.Paths;
41import java.nio.file.StandardCopyOption;
42import java.util.ArrayList;
43import java.util.Collections;
44import java.util.HashSet;
45import java.util.Iterator;
46import java.util.List;
47import java.util.Optional;
48import java.util.ServiceLoader;
49import java.util.ServiceLoader.Provider;
50import java.util.Set;
51import java.util.stream.Collectors;
52import javax.script.ScriptEngineFactory;
53
54import org.testng.annotations.Test;
55import org.testng.annotations.BeforeTest;
56import static org.testng.Assert.*;
57
58/**
59 * Basic test for ServiceLoader. The test make use of two service providers:
60 * 1. BananaScriptEngine - a ScriptEngineFactory deployed as a module on the
61 *    module path. It implementations a singleton via the public static
62 *    provider method.
63 * 2. PearScriptEngine - a ScriptEngineFactory deployed on the class path
64 *    with a service configuration file.
65 */
66
67public class ModulesTest {
68
69    // Copy the services configuration file for "pearscript" into place.
70    @BeforeTest
71    public void setup() throws Exception {
72        Path src = Paths.get(System.getProperty("test.src"));
73        Path classes = Paths.get(System.getProperty("test.classes"));
74        String st = ScriptEngineFactory.class.getName();
75        Path config = Paths.get("META-INF", "services", st);
76        Path source = src.resolve("classpath").resolve("pearscript").resolve(config);
77        Path target = classes.resolve(config);
78        Files.createDirectories(target.getParent());
79        Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
80    }
81
82    /**
83     * Basic test of iterator() to ensure that providers located as modules
84     * and on the class path are found.
85     */
86    @Test
87    public void testIterator() {
88        ServiceLoader<ScriptEngineFactory> loader
89            = ServiceLoader.load(ScriptEngineFactory.class);
90        Set<String> names = collectAll(loader)
91                .stream()
92                .map(ScriptEngineFactory::getEngineName)
93                .collect(Collectors.toSet());
94        assertTrue(names.contains("BananaScriptEngine"));
95        assertTrue(names.contains("PearScriptEngine"));
96    }
97
98    /**
99     * Basic test of iterator() to test iteration order. Providers deployed
100     * as named modules should be found before providers deployed on the class
101     * path.
102     */
103    @Test
104    public void testIteratorOrder() {
105        ServiceLoader<ScriptEngineFactory> loader
106            = ServiceLoader.load(ScriptEngineFactory.class);
107        boolean foundUnnamed = false;
108        for (ScriptEngineFactory factory : collectAll(loader)) {
109            if (factory.getClass().getModule().isNamed()) {
110                if (foundUnnamed) {
111                    assertTrue(false, "Named module element after unnamed");
112                }
113            } else {
114                foundUnnamed = true;
115            }
116        }
117    }
118
119    /**
120     * Basic test of Provider::type
121     */
122    @Test
123    public void testProviderType() {
124        Set<String> types = ServiceLoader.load(ScriptEngineFactory.class)
125                .stream()
126                .map(Provider::type)
127                .map(Class::getName)
128                .collect(Collectors.toSet());
129        assertTrue(types.contains("org.banana.BananaScriptEngineFactory"));
130        assertTrue(types.contains("org.pear.PearScriptEngineFactory"));
131    }
132
133    /**
134     * Basic test of Provider::get
135     */
136    @Test
137    public void testProviderGet() {
138        Set<String> names = ServiceLoader.load(ScriptEngineFactory.class)
139                .stream()
140                .map(Provider::get)
141                .map(ScriptEngineFactory::getEngineName)
142                .collect(Collectors.toSet());
143        assertTrue(names.contains("BananaScriptEngine"));
144        assertTrue(names.contains("PearScriptEngine"));
145    }
146
147    /**
148     * Basic test of the public static provider method. BananaScriptEngine
149     * defines a provider method that returns the same instance.
150     */
151    @Test
152    public void testSingleton() {
153        Optional<Provider<ScriptEngineFactory>> oprovider
154            = ServiceLoader.load(ScriptEngineFactory.class)
155                .stream()
156                .filter(p -> p.type().getName().equals("org.banana.BananaScriptEngineFactory"))
157                .findFirst();
158        assertTrue(oprovider.isPresent());
159        Provider<ScriptEngineFactory> provider = oprovider.get();
160
161        // invoke Provider::get twice
162        ScriptEngineFactory factory1 = provider.get();
163        ScriptEngineFactory factory2 = provider.get();
164        assertTrue(factory1 == factory2);
165    }
166
167    /**
168     * Basic test of stream() to ensure that elements for providers in named
169     * modules come before elements for providers in unnamed modules.
170     */
171    @Test
172    public void testStreamOrder() {
173        List<Class<?>> types = ServiceLoader.load(ScriptEngineFactory.class)
174                .stream()
175                .map(Provider::type)
176                .collect(Collectors.toList());
177
178        boolean foundUnnamed = false;
179        for (Class<?> factoryClass : types) {
180            if (factoryClass.getModule().isNamed()) {
181                if (foundUnnamed) {
182                    assertTrue(false, "Named module element after unnamed");
183                }
184            } else {
185                foundUnnamed = true;
186            }
187        }
188    }
189
190    /**
191     * Basic test of ServiceLoader.findFirst()
192     */
193    @Test
194    public void testFindFirst() {
195        Optional<ScriptEngineFactory> ofactory
196            = ServiceLoader.load(ScriptEngineFactory.class).findFirst();
197        assertTrue(ofactory.isPresent());
198        ScriptEngineFactory factory = ofactory.get();
199        assertTrue(factory.getClass().getModule().isNamed());
200
201        class S { }
202        assertFalse(ServiceLoader.load(S.class).findFirst().isPresent());
203    }
204
205    /**
206     * Basic test ServiceLoader.load specifying the platform class loader.
207     * The providers on the module path and class path should not be located.
208     */
209    @Test
210    public void testWithPlatformClassLoader() {
211        ClassLoader pcl = ClassLoader.getPlatformClassLoader();
212
213        // iterator
214        ServiceLoader<ScriptEngineFactory> loader
215            = ServiceLoader.load(ScriptEngineFactory.class, pcl);
216        Set<String> names = collectAll(loader)
217                .stream()
218                .map(ScriptEngineFactory::getEngineName)
219                .collect(Collectors.toSet());
220        assertFalse(names.contains("BananaScriptEngine"));
221        assertFalse(names.contains("PearScriptEngine"));
222
223        // stream
224        names = ServiceLoader.load(ScriptEngineFactory.class, pcl)
225                .stream()
226                .map(Provider::get)
227                .map(ScriptEngineFactory::getEngineName)
228                .collect(Collectors.toSet());
229        assertFalse(names.contains("BananaScriptEngine"));
230        assertFalse(names.contains("PearScriptEngine"));
231    }
232
233    /**
234     * Basic test of ServiceLoader.load where the service provider module is an
235     * automatic module.
236     */
237    @Test
238    public void testWithAutomaticModule() throws Exception {
239        Path classes = Paths.get(System.getProperty("test.classes"));
240        Path jar = Files.createTempDirectory("lib").resolve("pearscript.jar");
241        JarUtils.createJarFile(jar, classes, "META-INF", "org");
242
243        ModuleFinder finder = ModuleFinder.of(jar);
244        ModuleLayer bootLayer = ModuleLayer.boot();
245        Configuration parent = bootLayer.configuration();
246        Configuration cf = parent.resolveAndBind(finder, ModuleFinder.of(), Set.of());
247        assertTrue(cf.modules().size() == 1);
248
249        ClassLoader scl = ClassLoader.getSystemClassLoader();
250        ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
251        assertTrue(layer.modules().size() == 1);
252
253        ClassLoader loader = layer.findLoader("pearscript");
254        ScriptEngineFactory factory;
255
256        // load using the class loader as context
257        factory = ServiceLoader.load(ScriptEngineFactory.class, loader)
258                .findFirst()
259                .orElse(null);
260        assertNotNull(factory);
261        assertTrue(factory.getClass().getClassLoader() == loader);
262
263        // load using the layer as context
264        factory = ServiceLoader.load(layer, ScriptEngineFactory.class)
265                .findFirst()
266                .orElse(null);
267        assertNotNull(factory);
268        assertTrue(factory.getClass().getClassLoader() == loader);
269    }
270
271    /**
272     * Basic test of ServiceLoader.load, using the class loader for
273     * a module in a custom layer as the context.
274     */
275    @Test
276    public void testWithCustomLayer1() {
277        ModuleLayer layer = createCustomLayer("bananascript");
278
279        ClassLoader loader = layer.findLoader("bananascript");
280        List<ScriptEngineFactory> providers
281            = collectAll(ServiceLoader.load(ScriptEngineFactory.class, loader));
282
283        // should have at least 2 x bananascript + pearscript
284        assertTrue(providers.size() >= 3);
285
286        // first element should be the provider in the custom layer
287        ScriptEngineFactory factory = providers.get(0);
288        assertTrue(factory.getClass().getClassLoader() == loader);
289        assertTrue(factory.getClass().getModule().getLayer() == layer);
290        assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
291
292        // remainder should be the boot layer
293        providers.remove(0);
294        Set<String> names = providers.stream()
295                .map(ScriptEngineFactory::getEngineName)
296                .collect(Collectors.toSet());
297        assertTrue(names.contains("BananaScriptEngine"));
298        assertTrue(names.contains("PearScriptEngine"));
299    }
300
301    /**
302     * Basic test of ServiceLoader.load using a custom Layer as the context.
303     */
304    @Test
305    public void testWithCustomLayer2() {
306        ModuleLayer layer = createCustomLayer("bananascript");
307
308        List<ScriptEngineFactory> factories
309            = collectAll(ServiceLoader.load(layer, ScriptEngineFactory.class));
310
311        // should have at least 2 x bananascript
312        assertTrue(factories.size() >= 2);
313
314        // first element should be the provider in the custom layer
315        ScriptEngineFactory factory = factories.get(0);
316        assertTrue(factory.getClass().getModule().getLayer() == layer);
317        assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
318
319        // remainder should be the boot layer
320        factories.remove(0);
321        Set<String> names = factories.stream()
322                .map(ScriptEngineFactory::getEngineName)
323                .collect(Collectors.toSet());
324        assertTrue(names.contains("BananaScriptEngine"));
325        assertFalse(names.contains("PearScriptEngine"));
326    }
327
328    /**
329     * Basic test of ServiceLoader.load with a tree of layers.
330     *
331     * Test scenario:
332     * - boot layer contains "bananascript", maybe other script engines
333     * - layer1, with boot layer as parent, contains "bananascript"
334     * - layer2, with boot layer as parent, contains "bananascript"
335     * - layer3, with layer1 ad layer as parents, contains "bananascript"
336     *
337     * ServiceLoader should locate all 4 script engine factories in DFS order.
338     */
339    @Test
340    public void testWithCustomLayer3() {
341        ModuleLayer bootLayer = ModuleLayer.boot();
342        Configuration cf0 = bootLayer.configuration();
343
344        // boot layer should contain "bananascript"
345        List<ScriptEngineFactory> factories
346            = collectAll(ServiceLoader.load(bootLayer, ScriptEngineFactory.class));
347        int countInBootLayer = factories.size();
348        assertTrue(countInBootLayer >= 1);
349        assertTrue(factories.stream()
350                .map(p -> p.getEngineName())
351                .filter("BananaScriptEngine"::equals)
352                .findAny()
353                .isPresent());
354
355        ClassLoader scl = ClassLoader.getSystemClassLoader();
356        Path dir = Paths.get(System.getProperty("test.classes", "."), "modules");
357        ModuleFinder finder = ModuleFinder.of(dir);
358
359        // layer1
360        Configuration cf1 = cf0.resolveAndBind(finder, ModuleFinder.of(), Set.of());
361        ModuleLayer layer1 = bootLayer.defineModulesWithOneLoader(cf1, scl);
362        assertTrue(layer1.modules().size() == 1);
363
364        // layer2
365        Configuration cf2 = cf0.resolveAndBind(finder, ModuleFinder.of(), Set.of());
366        ModuleLayer layer2 = bootLayer.defineModulesWithOneLoader(cf2, scl);
367        assertTrue(layer2.modules().size() == 1);
368
369        // layer3 with layer1 and layer2 as parents
370        Configuration cf3 = Configuration.resolveAndBind(finder,
371                List.of(cf1, cf2),
372                ModuleFinder.of(),
373                Set.of());
374        ModuleLayer layer3
375            = ModuleLayer.defineModulesWithOneLoader(cf3, List.of(layer1, layer2), scl).layer();
376        assertTrue(layer3.modules().size() == 1);
377
378
379        // class loaders
380        ClassLoader loader1 = layer1.findLoader("bananascript");
381        ClassLoader loader2 = layer2.findLoader("bananascript");
382        ClassLoader loader3 = layer3.findLoader("bananascript");
383        assertTrue(loader1 != loader2);
384        assertTrue(loader1 != loader3);
385        assertTrue(loader2 != loader3);
386
387        // load all factories with layer3 as the context
388        factories = collectAll(ServiceLoader.load(layer3, ScriptEngineFactory.class));
389        int count = factories.size();
390        assertTrue(count == countInBootLayer + 3);
391
392        // the ordering should be layer3, layer1, boot layer, layer2
393
394        ScriptEngineFactory factory = factories.get(0);
395        assertTrue(factory.getClass().getModule().getLayer() == layer3);
396        assertTrue(factory.getClass().getClassLoader() == loader3);
397        assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
398
399        factory = factories.get(1);
400        assertTrue(factory.getClass().getModule().getLayer() == layer1);
401        assertTrue(factory.getClass().getClassLoader() == loader1);
402        assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
403
404        // boot layer "bananascript" and maybe other factories
405        int last = count -1;
406        boolean found = false;
407        for (int i=2; i<last; i++) {
408            factory = factories.get(i);
409            assertTrue(factory.getClass().getModule().getLayer() == bootLayer);
410            if (factory.getEngineName().equals("BananaScriptEngine")) {
411                assertFalse(found);
412                found = true;
413            }
414        }
415        assertTrue(found);
416
417        factory = factories.get(last);
418        assertTrue(factory.getClass().getModule().getLayer() == layer2);
419        assertTrue(factory.getClass().getClassLoader() == loader2);
420        assertTrue(factory.getEngineName().equals("BananaScriptEngine"));
421    }
422
423
424    // -- nulls --
425
426    @Test(expectedExceptions = { NullPointerException.class })
427    public void testLoadNull1() {
428        ServiceLoader.load(null);
429    }
430
431    @Test(expectedExceptions = { NullPointerException.class })
432    public void testLoadNull2() {
433        ServiceLoader.load((Class<?>) null, ClassLoader.getSystemClassLoader());
434    }
435
436    @Test(expectedExceptions = { NullPointerException.class })
437    public void testLoadNull3() {
438        class S { }
439        ServiceLoader.load((ModuleLayer) null, S.class);
440    }
441
442    @Test(expectedExceptions = { NullPointerException.class })
443    public void testLoadNull4() {
444        ServiceLoader.load(ModuleLayer.empty(), null);
445    }
446
447    @Test(expectedExceptions = { NullPointerException.class })
448    public void testLoadNull5() {
449        ServiceLoader.loadInstalled(null);
450    }
451
452    /**
453     * Create a custom layer by resolving the given module names. The modules
454     * are located in the {@code ${test.classes}/modules} directory.
455     */
456    private ModuleLayer createCustomLayer(String... modules) {
457        Path dir = Paths.get(System.getProperty("test.classes", "."), "modules");
458        ModuleFinder finder = ModuleFinder.of(dir);
459        Set<String> roots = new HashSet<>();
460        Collections.addAll(roots, modules);
461        ModuleLayer bootLayer = ModuleLayer.boot();
462        Configuration parent = bootLayer.configuration();
463        Configuration cf = parent.resolve(finder, ModuleFinder.of(), roots);
464        ClassLoader scl = ClassLoader.getSystemClassLoader();
465        ModuleLayer layer = bootLayer.defineModulesWithOneLoader(cf, scl);
466        assertTrue(layer.modules().size() == 1);
467        return layer;
468    }
469
470    private <E> List<E> collectAll(ServiceLoader<E> loader) {
471        List<E> list = new ArrayList<>();
472        Iterator<E> iterator = loader.iterator();
473        while (iterator.hasNext()) {
474            list.add(iterator.next());
475        }
476        return list;
477    }
478}
479
480