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