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