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
ServiceLoader$ProviderIterator
64%
16/25
50%
2/4
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  
 }