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  
23  import java.io.BufferedReader;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.io.InputStreamReader;
27  import java.net.URL;
28  import java.util.Enumeration;
29  import java.util.HashSet;
30  import java.util.Set;
31  
32  import org.codehaus.plexus.component.annotations.Component;
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  @Component(role = ServiceLoader.class)
48  public class ServiceLoader {
49  
50      @Nonnull
51      @SuppressWarnings("unchecked")
52      public <T> Set<T> load(Class<T> clazz, ClassLoader classLoader) {
53          try {
54              Set<T> implementations = new HashSet<>();
55              for (String fullyQualifiedClassName : lookup(clazz, classLoader)) {
56                  Class<?> implClass = classLoader.loadClass(fullyQualifiedClassName);
57                  implementations.add((T) getConstructor(implClass).newInstance());
58              }
59              return implementations;
60          } catch (IOException | ReflectiveOperationException e) {
61              throw new IllegalStateException(e.getLocalizedMessage(), e);
62          }
63      }
64  
65      @Nonnull
66      public Set<String> lookup(Class<?> clazz, ClassLoader classLoader) throws IOException {
67          final String resourceName = "META-INF/services/" + clazz.getName();
68  
69          if (classLoader == null) {
70              return emptySet();
71          }
72          final Enumeration<URL> urls = classLoader.getResources(resourceName);
73          return lookupSpiImplementations(urls);
74      }
75  
76      /**
77       * Method loadServices loads the services of a class that are
78       * defined using the SPI mechanism.
79       *
80       * @param urlEnumeration The urls from the resource
81       * @return The set of service provider names
82       * @throws IOException When reading the streams fails
83       */
84      @Nonnull
85      @SuppressWarnings("checkstyle:innerassignment")
86      private static Set<String> lookupSpiImplementations(final Enumeration<URL> urlEnumeration) throws IOException {
87          final Set<String> names = new HashSet<>();
88          nextUrl:
89          while (urlEnumeration.hasMoreElements()) {
90              final URL url = urlEnumeration.nextElement();
91              try (BufferedReader reader = getReader(url)) {
92                  for (String line; (line = reader.readLine()) != null; ) {
93                      int ci = line.indexOf('#');
94                      if (ci >= 0) {
95                          line = line.substring(0, ci);
96                      }
97                      line = line.trim();
98                      int n = line.length();
99                      if (n == 0) {
100                         continue; // next line
101                     }
102 
103                     if (line.indexOf(' ') >= 0 || line.indexOf('\t') >= 0) {
104                         continue nextUrl; // next url
105                     }
106                     char cp = line.charAt(0); // should use codePointAt but this was JDK1.3
107                     if (!isJavaIdentifierStart(cp)) {
108                         continue nextUrl; // next url
109                     }
110                     for (int i = 1; i < n; i++) {
111                         cp = line.charAt(i); // should use codePointAt but this was JDK1.3
112                         if (!isJavaIdentifierPart(cp) && cp != '.') {
113                             continue nextUrl; // next url
114                         }
115                     }
116                     if (!names.contains(line)) {
117                         names.add(line);
118                     }
119                 }
120             }
121         }
122 
123         return names;
124     }
125 
126     @Nonnull
127     private static BufferedReader getReader(@Nonnull URL url) throws IOException {
128         final InputStream inputStream = url.openStream();
129         final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
130         return new BufferedReader(inputStreamReader);
131     }
132 }