View Javadoc

1   package org.nnsoft.commons.bspi;
2   
3   /*
4    *    Copyright 2010-2011 The 99 Software Foundation
5    *
6    *    Licensed under the Apache License, Version 2.0 (the "License");
7    *    you may not use this file except in compliance with the License.
8    *    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, software
13   *    distributed under the License is distributed on an "AS IS" BASIS,
14   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   *    See the License for the specific language governing permissions and
16   *    limitations under the License.
17   */
18  
19  import java.io.Closeable;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.io.Reader;
24  import java.net.URL;
25  import java.nio.charset.Charset;
26  import java.util.Enumeration;
27  import java.util.Iterator;
28  import java.util.LinkedHashMap;
29  import java.util.NoSuchElementException;
30  
31  /**
32   * @param <S> The type of the service to be loaded by this loader.
33   * @since 1.0.1
34   */
35  final class ServiceClassIterator<S>
36      implements Iterator<Class<? extends S>>
37  {
38  
39      /**
40       * The default <code>UTF-8</code> character encoding.
41       */
42      private static final Charset UTF_8 = Charset.forName( "UTF-8" );
43  
44      /**
45       * The class or interface representing the service being loaded.
46       */
47      private final Class<S> service;
48  
49      /**
50       * The class loader used to locate, load, and instantiate providers.
51       */
52      private final ClassLoader classLoader;
53  
54      private final Enumeration<URL> serviceResources;
55  
56      /**
57       * Cached providers types, in instantiation order.
58       */
59      private final LinkedHashMap<String, Class<? extends S>> providerTypes;
60  
61      private Iterator<String> pending = null;
62  
63      private String nextName = null;
64  
65      /**
66       * @param service the class or interface representing the service being loaded.
67       * @param classLoader the class loader used to locate, load, and instantiate providers.
68       * @param serviceResources
69       * @param providerTypes cached providers types, in instantiation order.
70       */
71      public ServiceClassIterator( Class<S> service, ClassLoader classLoader, Enumeration<URL> serviceResources,
72                                   LinkedHashMap<String, Class<? extends S>> providerTypes )
73      {
74          this.service = service;
75          this.classLoader = classLoader;
76          this.serviceResources = serviceResources;
77          this.providerTypes = providerTypes;
78      }
79  
80      /**
81       * {@inheritDoc}
82       */
83      public boolean hasNext()
84      {
85          if ( nextName != null )
86          {
87              return true;
88          }
89  
90          while ( ( pending == null ) || !pending.hasNext() )
91          {
92              if ( !serviceResources.hasMoreElements() )
93              {
94                  return false;
95              }
96              pending = parseServiceFile( serviceResources.nextElement() );
97          }
98  
99          nextName = pending.next();
100         return true;
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     public Class<? extends S> next()
107     {
108         if ( !hasNext() )
109         {
110             throw new NoSuchElementException();
111         }
112         String className = nextName;
113         nextName = null;
114         try
115         {
116             Class<?> clazz = classLoader.loadClass( className );
117             if ( !service.isAssignableFrom( clazz ) )
118             {
119                 throw new ServiceConfigurationError( "Provider '" + className + "' is not assignable to Service '"
120                     + service.getName() + "'" );
121             }
122             Class<? extends S> serviceClass = clazz.asSubclass( service );
123             providerTypes.put( className, serviceClass );
124             return serviceClass;
125         }
126         catch ( ClassNotFoundException e )
127         {
128             throw new ServiceConfigurationError( "Provider '" + className + "' not found", e );
129         }
130         catch ( ClassCastException e )
131         {
132             throw new ServiceConfigurationError( "Provider '"
133                                                  + className
134                                                  + "' is not assignable to Service '"
135                                                  + service.getName()
136                                                  + "'", e );
137         }
138         catch ( Throwable e )
139         {
140             throw new ServiceConfigurationError( "Provider '" + className + "' could not be instantiated", e );
141         }
142     }
143 
144     /**
145      * {@inheritDoc}
146      */
147     public void remove()
148     {
149         throw new UnsupportedOperationException();
150     }
151 
152     /**
153      * Parse the content of the given URL as a provider-configuration file.
154      *
155      * @param url the URL naming the configuration file to be parsed.
156      * @return a (possibly empty) iterator that will yield the provider-class names in the given configuration file that
157      *         are not yet members of the returned set
158      */
159     private Iterator<String> parseServiceFile( URL url )
160     {
161         InputStream inputStream = null;
162         Reader reader = null;
163         try
164         {
165             inputStream = url.openStream();
166             reader = new InputStreamReader( inputStream, UTF_8 );
167             ServiceFileParser<S> serviceFileParser = new ServiceFileParser<S>( reader );
168             serviceFileParser.setProviderTypes( providerTypes );
169             serviceFileParser.parse();
170             return serviceFileParser.iterator();
171         }
172         catch ( Exception e )
173         {
174             throw new ServiceConfigurationError( "An error occurred while reading service resource '"
175                                                  + url
176                                                  + "' for service class '"
177                                                  + service.getName()
178                                                  + "'", e );
179         }
180         finally
181         {
182             closeQuietly( reader );
183             closeQuietly( inputStream );
184         }
185     }
186 
187     /**
188      * Unconditionally close a {@link Closeable} element.
189      *
190      * @param closeable the {@link Closeable} element.
191      */
192     private static void closeQuietly( Closeable closeable )
193     {
194         if ( closeable != null )
195         {
196             try
197             {
198                 closeable.close();
199             }
200             catch ( IOException e )
201             {
202                 // close quietly
203             }
204         }
205     }
206 
207 }