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  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 static java.lang.Character.isJavaIdentifierPart;
33  import static java.lang.Character.isJavaIdentifierStart;
34  import static java.util.Collections.emptySet;
35  import static org.apache.maven.surefire.util.ReflectionUtils.getConstructor;
36  
37  /**
38   * SPI loader for Surefire/Failsafe should use {@link Thread#getContextClassLoader() current ClassLoader}.
39   * <br>
40   * The {@link java.util.ServiceLoader} embedded in JVM uses
41   * {@link ClassLoader#getSystemClassLoader() System ClassLoader} and cannot be used in Surefire/Failsafe.
42   *
43   * @since 2.20
44   */
45  public final class ServiceLoader
46  {
47  
48      @Nonnull
49      @SuppressWarnings( "unchecked" )
50      public <T> Set<T> load( Class<T> clazz, ClassLoader classLoader )
51      {
52          try
53          {
54              Set<T> implementations = new HashSet<>();
55              for ( String fullyQualifiedClassName : lookup( clazz, classLoader ) )
56              {
57                  Class<?> implClass = classLoader.loadClass( fullyQualifiedClassName );
58                  implementations.add( (T) getConstructor( implClass ).newInstance() );
59              }
60              return implementations;
61          }
62          catch ( IOException | ReflectiveOperationException e )
63          {
64              throw new IllegalStateException( e.getLocalizedMessage(), e );
65          }
66      }
67  
68      @Nonnull
69      public Set<String> lookup( Class<?> clazz, ClassLoader classLoader )
70              throws IOException
71      {
72          final String resourceName = "META-INF/services/" + clazz.getName();
73  
74          if ( classLoader == null )
75          {
76              return emptySet();
77          }
78          final Enumeration<URL> urls = classLoader.getResources( resourceName );
79          return lookupSpiImplementations( urls );
80      }
81  
82      /**
83       * Method loadServices loads the services of a class that are
84       * defined using the SPI mechanism.
85       *
86       * @param urlEnumeration The urls from the resource
87       * @return The set of service provider names
88       * @throws IOException When reading the streams fails
89       */
90      @Nonnull
91      @SuppressWarnings( "checkstyle:innerassignment" )
92      private static Set<String> lookupSpiImplementations( final Enumeration<URL> urlEnumeration )
93              throws IOException
94      {
95          final Set<String> names = new HashSet<>();
96          nextUrl:
97          while ( urlEnumeration.hasMoreElements() )
98          {
99              final URL url = urlEnumeration.nextElement();
100             try ( BufferedReader reader = getReader( url ) )
101             {
102                 for ( String line; ( line = reader.readLine() ) != null; )
103                 {
104                     int ci = line.indexOf( '#' );
105                     if ( ci >= 0 )
106                     {
107                         line = line.substring( 0, ci );
108                     }
109                     line = line.trim();
110                     int n = line.length();
111                     if ( n == 0 )
112                     {
113                         continue; // next line
114                     }
115 
116                     if ( line.indexOf( ' ' ) >= 0 || line.indexOf( '\t' ) >= 0 )
117                     {
118                         continue nextUrl; // next url
119                     }
120                     char cp = line.charAt( 0 ); // should use codePointAt but this was JDK1.3
121                     if ( !isJavaIdentifierStart( cp ) )
122                     {
123                         continue nextUrl; // next url
124                     }
125                     for ( int i = 1; i < n; i++ )
126                     {
127                         cp = line.charAt( i );  // should use codePointAt but this was JDK1.3
128                         if ( !isJavaIdentifierPart( cp ) && cp != '.' )
129                         {
130                             continue nextUrl; // next url
131                         }
132                     }
133                     if ( !names.contains( line ) )
134                     {
135                         names.add( line );
136                     }
137                 }
138             }
139         }
140 
141         return names;
142     }
143 
144     @Nonnull
145     private static BufferedReader getReader( @Nonnull URL url )
146             throws IOException
147     {
148         final InputStream inputStream = url.openStream();
149         final InputStreamReader inputStreamReader = new InputStreamReader( inputStream );
150         return new BufferedReader( inputStreamReader );
151     }
152 }