##### Coverage Report - org.seasar.cubby.internal.util.ServiceLoader

Classes in this File | Line Coverage | Branch Coverage | Complexity
ServiceLoader |
95% | 46/48
83% | 15/18
| 3.0;3
ServiceLoader$ProviderIterator |
64% | 16/25
50% | 2/4
| 3.0;3

1 |  |
     /*
2 |  |
      * Copyright 2004-2009 the Seasar Foundation and the Others.
3 |  |
      *
4 |  |
      * Licensed under the Apache License, Version 2.0 (the "License");
5 |  |
      * you may not use this file except in compliance with the License.
6 |  |
      * You may obtain a copy of the License at
7 |  |
      *
8 |  |
      *     http://www.apache.org/licenses/LICENSE-2.0
9 |  |
      *
10 |  |
      * Unless required by applicable law or agreed to in writing, software
11 |  |
      * distributed under the License is distributed on an "AS IS" BASIS,
12 |  |
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13 |  |
      * either express or implied. See the License for the specific language
14 |  |
      * governing permissions and limitations under the License.
15 |  |
      */
16 |  |
     package org.seasar.cubby.internal.util;
17 |  |
18 |  |
     import static org.seasar.cubby.internal.util.LogMessages.format;
19 |  |
20 |  |
     import java.io.BufferedReader;
21 |  |
     import java.io.IOException;
22 |  |
     import java.io.InputStream;
23 |  |
     import java.io.InputStreamReader;
24 |  |
     import java.net.URL;
25 |  |
     import java.util.ArrayList;
26 |  |
     import java.util.Enumeration;
27 |  |
     import java.util.Iterator;
28 |  |
     import java.util.LinkedHashMap;
29 |  |
     import java.util.List;
30 |  |
     import java.util.Map;
31 |  |
     import java.util.Map.Entry;
32 |  |
33 |  |
     import org.slf4j.Logger;
34 |  |
     import org.slf4j.LoggerFactory;
35 |  |
36 |  |
     /**
37 |  |
      * サービスプロバイダロード機構です。
38 |  |
      * <p>
39 |  |
      * サービスプロバイダは、リソースディレクトリ <code>META-INF/services</code>
40 |  |
      * に「プロバイダ構成ファイル」を配置することによって識別されます。
41 |  |
      * このファイルの名前は、サービスの型の完全修飾名になります。このファイルには、具象プロバイダクラスの完全修飾名が 1 行に 1
42 |  |
      * つずつ記述されます。それぞれの名前を囲む空白文字とタブ文字、および空白行は無視されます。コメント文字は '#' ('\u0023'、NUMBER
43 |  |
      * SIGN) です。 行頭にコメント文字が挿入されている場合、その行のすべての文字は無視されます。ファイルは UTF-8 で符号化されている必要があります。
44 |  |
      * </p>
45 |  |
      *
46 |  |
      * @param <S>
47 |  |
      *            このローダーによってロードされるサービスの型
48 |  |
      * @author baba
49 |  |
      */
50 | 11 |
     public class ServiceLoader<S> implements Iterable<S> {
51 |  |
52 |  |
             /** ロガー。 */
53 | 1 |
             private static final Logger logger = LoggerFactory
54 |  |
                             .getLogger(ServiceLoader.class);
55 |  |
56 |  |
             /** リソースディレクトリ。 */
57 |  |
             private static final String PREFIX = "META-INF/services/";
58 |  |
59 |  |
             /** プロバイダのキャッシュ。 */
60 | 1 |
             private final Map<String, S> providers = new LinkedHashMap<String, S>();
61 |  |
62 |  |
             /** サービスの型。 */
63 |  |
             private final Class<S> service;
64 |  |
65 |  |
             /** クラスローダ。 */
66 |  |
             private final ClassLoader classLoader;
67 |  |
68 |  |
             /**
69 |  |
              * 指定されたサービスのローダーを構築します。
70 |  |
              *
71 |  |
              * @param service
72 |  |
              *            サービスを表すインターフェイス
73 |  |
              * @param classLoader
74 |  |
              *            プロバイダ構成ファイルとプロバイダクラスのロードに使用するクラスローダー
75 |  |
              */
76 | 1 |
             private ServiceLoader(final Class<S> service, final ClassLoader classLoader) {
77 | 1 |
                     this.service = service;
78 | 1 |
                     this.classLoader = classLoader;
79 | 1 |
                     reload();
80 | 1 |
             }
81 |  |
82 |  |
             /**
83 |  |
              * プロバイダを再ロードします。
84 |  |
              */
85 |  |
             public void reload() {
86 | 1 |
                     providers.clear();
87 |  |
88 | 1 |
                     final String resourceName = PREFIX + service.getName();
89 |  |
                     try {
90 | 1 |
                             final Enumeration<URL> urls = classLoader
91 |  |
                                             .getResources(resourceName);
92 | 2 |
                             while (urls.hasMoreElements()) {
93 | 1 |
                                     final URL url = urls.nextElement();
94 | 1 |
                                     for (final String providerClassName : parse(url)) {
95 | 2 |
                                             providers.put(providerClassName, null);
96 |  |
                                     }
97 | 1 |
                             }
98 | 0 |
                     } catch (final IOException e) {
99 | 0 |
                             throw new ServiceLoadingException(e);
100 | 1 |
                     }
101 | 1 |
             }
102 |  |
103 |  |
             /**
104 |  |
              * 指定された URL からプロバイダ構成ファイルを読み込み、パースします。
105 |  |
              *
106 |  |
              * @param url
107 |  |
              *            プロバイダ構成ファイルの URL
108 |  |
              * @return プロバイダを実装したクラスの <code>List</code>
109 |  |
              * @throws IOException
110 |  |
              *             プロバイダ構成ファイルの読み込み時に {@link IOException} が発生した場合
111 |  |
              */
112 |  |
             private List<String> parse(final URL url) throws IOException {
113 | 1 |
                     if (logger.isDebugEnabled()) {
114 | 1 |
                             logger.debug(format("DCUB0017", service, url));
115 |  |
                     }
116 | 1 |
                     final List<String> providerClassNames = new ArrayList<String>();
117 | 1 |
                     InputStream input = null;
118 | 1 |
                     BufferedReader reader = null;
119 |  |
                     try {
120 | 1 |
                             input = url.openStream();
121 | 1 |
                             reader = new BufferedReader(new InputStreamReader(input, "utf-8"));
122 | 6 |
                             for (String line; (line = reader.readLine()) != null;) {
123 | 5 |
                                     final String providerClassName = cleanup(line);
124 | 5 |
                                     if (providerClassName != null) {
125 | 2 |
                                             providerClassNames.add(providerClassName);
126 |  |
                                     }
127 | 5 |
                             }
128 |  |
                     } finally {
129 | 1 |
                             if (reader != null) {
130 | 1 |
                                     reader.close();
131 |  |
                             }
132 | 1 |
                             if (input != null) {
133 | 1 |
                                     input.close();
134 |  |
                             }
135 |  |
                     }
136 | 1 |
                     return providerClassNames;
137 |  |
             }
138 |  |
139 |  |
             /**
140 |  |
              * 指定された文字列からコメントや空白を取り除きます。
141 |  |
              *
142 |  |
              * @param line
143 |  |
              *            文字列
144 |  |
              * @return <code>line</code> からコメントや空白を取り除いた文字列
145 |  |
              */
146 |  |
             private String cleanup(String line) {
147 | 5 |
                     final int commentIndex = line.indexOf('#');
148 | 5 |
                     if (commentIndex >= 0) {
149 | 3 |
                             line = line.substring(0, commentIndex);
150 |  |
                     }
151 | 5 |
                     line = line.trim();
152 | 5 |
                     if (line.length() == 0) {
153 | 3 |
                             return null;
154 |  |
                     }
155 | 2 |
                     return line;
156 |  |
             }
157 |  |
158 |  |
             /**
159 |  |
              * プロバイダの {@link Iterator} を取得します。
160 |  |
              *
161 |  |
              * @return プロバイダの {@link Iterator}
162 |  |
              */
163 |  |
             public Iterator<S> iterator() {
164 | 1 |
                     return new ProviderIterator();
165 |  |
             }
166 |  |
167 |  |
             /**
168 |  |
              * 指定された型とクラスローダーに対応する新しいサービスローダーを作成します。
169 |  |
              *
170 |  |
              * @param <S>
171 |  |
              *            サービスの型
172 |  |
              * @param service
173 |  |
              *            サービスの型
174 |  |
              * @param classLoader
175 |  |
              *            クラスローダー
176 |  |
              * @return サービスローダー
177 |  |
              */
178 |  |
             public static <S> ServiceLoader<S> load(final Class<S> service,
179 |  |
                             final ClassLoader classLoader) {
180 | 1 |
                     return new ServiceLoader<S>(service, classLoader);
181 |  |
             }
182 |  |
183 |  |
             /**
184 |  |
              * 指定された型に対応する新しいサービスローダーを作成します。
185 |  |
              *
186 |  |
              * @param <S>
187 |  |
              *            サービスの型
188 |  |
              * @param service
189 |  |
              *            サービスの型
190 |  |
              * @return サービスローダー
191 |  |
              */
192 |  |
             public static <S> ServiceLoader<S> load(final Class<S> service) {
193 | 1 |
                     final ClassLoader classLoader = Thread.currentThread()
194 |  |
                                     .getContextClassLoader();
195 | 1 |
                     return load(service, classLoader);
196 |  |
             }
197 |  |
198 |  |
             /**
199 |  |
              * プロバイダの {@link Iterator}
200 |  |
              *
201 |  |
              * @author baba
202 |  |
              */
203 |  |
             private class ProviderIterator implements Iterator<S> {
204 |  |
205 |  |
                     /** プロバイダのキャッシュの {@link Iterator} */
206 |  |
                     private final Iterator<Entry<String, S>> providerIterator;
207 |  |
208 |  |
                     /**
209 |  |
                      * 新しい <code>ProviderIterator</code> を構築します。
210 |  |
                      */
211 | 1 |
                     ProviderIterator() {
212 | 1 |
                             providerIterator = providers.entrySet().iterator();
213 | 1 |
                     }
214 |  |
215 |  |
                     /**
216 |  |
                      * {@inheritDoc}
217 |  |
                      */
218 |  |
                     public boolean hasNext() {
219 | 3 |
                             return providerIterator.hasNext();
220 |  |
                     }
221 |  |
222 |  |
                     /**
223 |  |
                      * {@inheritDoc}
224 |  |
                      */
225 |  |
                     public S next() {
226 | 2 |
                             final Entry<String, S> entry = providerIterator.next();
227 | 2 |
                             if (entry.getValue() == null) {
228 | 2 |
                                     final String providerClassName = entry.getKey();
229 | 2 |
                                     final S provider = newInstance(providerClassName);
230 | 2 |
                                     entry.setValue(provider);
231 | 2 |
                                     if (logger.isDebugEnabled()) {
232 | 2 |
                                             logger.debug(format("DCUB0018", service, providerClassName,
233 |  |
                                                             provider));
234 |  |
                                     }
235 |  |
                             }
236 | 2 |
                             return entry.getValue();
237 |  |
                     }
238 |  |
239 |  |
                     /**
240 |  |
                      * 指定されたクラスのインスタンスを生成します。
241 |  |
                      *
242 |  |
                      * @param className
243 |  |
                      *            クラス名
244 |  |
                      * @return 生成したインスタンス
245 |  |
                      */
246 |  |
                     private S newInstance(final String className) {
247 |  |
                             try {
248 | 2 |
                                     final Class<?> providerClass = Class.forName(className, true,
249 |  |
                                                     classLoader);
250 | 2 |
                                     final Object providerInstance = providerClass.newInstance();
251 | 2 |
                                     final S provider = service.cast(providerInstance);
252 | 2 |
                                     return provider;
253 | 0 |
                             } catch (final ClassNotFoundException e) {
254 | 0 |
                                     throw new ServiceLoadingException(e);
255 | 0 |
                             } catch (final InstantiationException e) {
256 | 0 |
                                     throw new ServiceLoadingException(e);
257 | 0 |
                             } catch (final IllegalAccessException e) {
258 | 0 |
                                     throw new ServiceLoadingException(e);
259 | 0 |
                             } catch (final ClassCastException e) {
260 | 0 |
                                     throw new ServiceLoadingException(e);
261 |  |
                             }
262 |  |
                     }
263 |  |
264 |  |
                     /**
265 |  |
                      * {@inheritDoc}
266 |  |
                      */
267 |  |
                     public void remove() {
268 | 0 |
                             throw new UnsupportedOperationException();
269 |  |
                     }
270 |  |
271 |  |
             }
272 |  |
273 |  |
     }