View Javadoc

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  public class ServiceLoader<S> implements Iterable<S> {
51  
52  	/** ロガー。 */
53  	private static final Logger logger = LoggerFactory
54  			.getLogger(ServiceLoader.class);
55  
56  	/** リソースディレクトリ。 */
57  	private static final String PREFIX = "META-INF/services/";
58  
59  	/** プロバイダのキャッシュ。 */
60  	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  	private ServiceLoader(final Class<S> service, final ClassLoader classLoader) {
77  		this.service = service;
78  		this.classLoader = classLoader;
79  		reload();
80  	}
81  
82  	/**
83  	 * プロバイダを再ロードします。
84  	 */
85  	public void reload() {
86  		providers.clear();
87  
88  		final String resourceName = PREFIX + service.getName();
89  		try {
90  			final Enumeration<URL> urls = classLoader
91  					.getResources(resourceName);
92  			while (urls.hasMoreElements()) {
93  				final URL url = urls.nextElement();
94  				for (final String providerClassName : parse(url)) {
95  					providers.put(providerClassName, null);
96  				}
97  			}
98  		} catch (final IOException e) {
99  			throw new ServiceLoadingException(e);
100 		}
101 	}
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 		if (logger.isDebugEnabled()) {
114 			logger.debug(format("DCUB0017", service, url));
115 		}
116 		final List<String> providerClassNames = new ArrayList<String>();
117 		InputStream input = null;
118 		BufferedReader reader = null;
119 		try {
120 			input = url.openStream();
121 			reader = new BufferedReader(new InputStreamReader(input, "utf-8"));
122 			for (String line; (line = reader.readLine()) != null;) {
123 				final String providerClassName = cleanup(line);
124 				if (providerClassName != null) {
125 					providerClassNames.add(providerClassName);
126 				}
127 			}
128 		} finally {
129 			if (reader != null) {
130 				reader.close();
131 			}
132 			if (input != null) {
133 				input.close();
134 			}
135 		}
136 		return providerClassNames;
137 	}
138 
139 	/**
140 	 * 指定された文字列からコメントや空白を取り除きます。
141 	 * 
142 	 * @param line
143 	 *            文字列
144 	 * @return <code>line</code> からコメントや空白を取り除いた文字列
145 	 */
146 	private String cleanup(String line) {
147 		final int commentIndex = line.indexOf('#');
148 		if (commentIndex >= 0) {
149 			line = line.substring(0, commentIndex);
150 		}
151 		line = line.trim();
152 		if (line.length() == 0) {
153 			return null;
154 		}
155 		return line;
156 	}
157 
158 	/**
159 	 * プロバイダの {@link Iterator} を取得します。
160 	 * 
161 	 * @return プロバイダの {@link Iterator}
162 	 */
163 	public Iterator<S> iterator() {
164 		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 		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 		final ClassLoader classLoader = Thread.currentThread()
194 				.getContextClassLoader();
195 		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 		ProviderIterator() {
212 			providerIterator = providers.entrySet().iterator();
213 		}
214 
215 		/**
216 		 * {@inheritDoc}
217 		 */
218 		public boolean hasNext() {
219 			return providerIterator.hasNext();
220 		}
221 
222 		/**
223 		 * {@inheritDoc}
224 		 */
225 		public S next() {
226 			final Entry<String, S> entry = providerIterator.next();
227 			if (entry.getValue() == null) {
228 				final String providerClassName = entry.getKey();
229 				final S provider = newInstance(providerClassName);
230 				entry.setValue(provider);
231 				if (logger.isDebugEnabled()) {
232 					logger.debug(format("DCUB0018", service, providerClassName,
233 							provider));
234 				}
235 			}
236 			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 				final Class<?> providerClass = Class.forName(className, true,
249 						classLoader);
250 				final Object providerInstance = providerClass.newInstance();
251 				final S provider = service.cast(providerInstance);
252 				return provider;
253 			} catch (final ClassNotFoundException e) {
254 				throw new ServiceLoadingException(e);
255 			} catch (final InstantiationException e) {
256 				throw new ServiceLoadingException(e);
257 			} catch (final IllegalAccessException e) {
258 				throw new ServiceLoadingException(e);
259 			} catch (final ClassCastException e) {
260 				throw new ServiceLoadingException(e);
261 			}
262 		}
263 
264 		/**
265 		 * {@inheritDoc}
266 		 */
267 		public void remove() {
268 			throw new UnsupportedOperationException();
269 		}
270 
271 	}
272 
273 }