View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.surefire.providerapi;
20  
21  import javax.annotation.Nonnull;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.io.BufferedReader;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.net.URL;
30  import java.util.Enumeration;
31  import java.util.HashSet;
32  import java.util.Set;
33  
34  import static java.lang.Character.isJavaIdentifierPart;
35  import static java.lang.Character.isJavaIdentifierStart;
36  import static java.util.Collections.emptySet;
37  import static org.apache.maven.surefire.api.util.ReflectionUtils.getConstructor;
38  
39  /**
40   * SPI loader for Surefire/Failsafe should use {@link Thread#getContextClassLoader() current ClassLoader}.
41   * <br>
42   * The {@link java.util.ServiceLoader} embedded in JVM uses
43   * {@link ClassLoader#getSystemClassLoader() System ClassLoader} and cannot be used in Surefire/Failsafe.
44   *
45   * @since 2.20
46   */
47  @Singleton
48  @Named
49  public class ServiceLoader {
50  
51      @Nonnull
52      @SuppressWarnings("unchecked")
53      public <T> Set<T> load(Class<T> clazz, ClassLoader classLoader) {
54          try {
55              Set<T> implementations = new HashSet<>();
56              for (String fullyQualifiedClassName : lookup(clazz, classLoader)) {
57                  Class<?> implClass = classLoader.loadClass(fullyQualifiedClassName);
58                  implementations.add((T) getConstructor(implClass).newInstance());
59              }
60              return implementations;
61          } catch (IOException | ReflectiveOperationException e) {
62              throw new IllegalStateException(e.getLocalizedMessage(), e);
63          }
64      }
65  
66      @Nonnull
67      public Set<String> lookup(Class<?> clazz, ClassLoader classLoader) throws IOException {
68          final String resourceName = "META-INF/services/" + clazz.getName();
69  
70          if (classLoader == null) {
71              return emptySet();
72          }
73          final Enumeration<URL> urls = classLoader.getResources(resourceName);
74          return lookupSpiImplementations(urls);
75      }
76  
77      /**
78       * Method loadServices loads the services of a class that are
79       * defined using the SPI mechanism.
80       *
81       * @param urlEnumeration The urls from the resource
82       * @return The set of service provider names
83       * @throws IOException When reading the streams fails
84       */
85      @Nonnull
86      @SuppressWarnings("checkstyle:innerassignment")
87      private static Set<String> lookupSpiImplementations(final Enumeration<URL> urlEnumeration) throws IOException {
88          final Set<String> names = new HashSet<>();
89          nextUrl:
90          while (urlEnumeration.hasMoreElements()) {
91              final URL url = urlEnumeration.nextElement();
92              try (BufferedReader reader = getReader(url)) {
93                  for (String line; (line = reader.readLine()) != null; ) {
94                      int ci = line.indexOf('#');
95                      if (ci >= 0) {
96                          line = line.substring(0, ci);
97                      }
98                      line = line.trim();
99                      int n = line.length();
100                     if (n == 0) {
101                         continue; // next line
102                     }
103 
104                     if (line.indexOf(' ') >= 0 || line.indexOf('\t') >= 0) {
105                         continue nextUrl; // next url
106                     }
107                     char cp = line.charAt(0); // should use codePointAt but this was JDK1.3
108                     if (!isJavaIdentifierStart(cp)) {
109                         continue nextUrl; // next url
110                     }
111                     for (int i = 1; i < n; i++) {
112                         cp = line.charAt(i); // should use codePointAt but this was JDK1.3
113                         if (!isJavaIdentifierPart(cp) && cp != '.') {
114                             continue nextUrl; // next url
115                         }
116                     }
117                     if (!names.contains(line)) {
118                         names.add(line);
119                     }
120                 }
121             }
122         }
123 
124         return names;
125     }
126 
127     @Nonnull
128     private static BufferedReader getReader(@Nonnull URL url) throws IOException {
129         final InputStream inputStream = url.openStream();
130         final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
131         return new BufferedReader(inputStreamReader);
132     }
133 }