View Javadoc

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