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