001/**
002 *   GRANITE DATA SERVICES
003 *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 *   This file is part of the Granite Data Services Platform.
006 *
007 *   Granite Data Services is free software; you can redistribute it and/or
008 *   modify it under the terms of the GNU Lesser General Public
009 *   License as published by the Free Software Foundation; either
010 *   version 2.1 of the License, or (at your option) any later version.
011 *
012 *   Granite Data Services is distributed in the hope that it will be useful,
013 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 *   General Public License for more details.
016 *
017 *   You should have received a copy of the GNU Lesser General Public
018 *   License along with this library; if not, write to the Free Software
019 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 *   USA, or see <http://www.gnu.org/licenses/>.
021 */
022package org.granite.scan;
023
024import java.io.BufferedReader;
025import java.io.InputStream;
026import java.io.InputStreamReader;
027import java.net.URL;
028import java.util.ArrayList;
029import java.util.Enumeration;
030import java.util.Iterator;
031import java.util.List;
032import java.util.logging.Level;
033import java.util.logging.Logger;
034
035/**
036 * @author Franck WOLFF
037 */
038public class ServiceLoader<S> implements Iterable<S> {
039        
040        // Can't use granite logger here, because service loader can be used to load a specific
041        // logger implementation (stack overflow...)
042        private static final Logger jdkLog = Logger.getLogger(ServiceLoader.class.getName());
043        
044        private final Class<S> service;
045        private final ClassLoader loader;
046        
047        private List<String> serviceClassNames;
048        
049        private Class<?>[] constructorParameterTypes = new Class<?>[0];
050        private Object[] constructorParameters = new Object[0];
051        
052        private ServiceLoader(Class<S> service, ClassLoader loader, List<String> servicesNames) {
053                this.service = service;
054                this.loader = loader;
055                this.serviceClassNames = servicesNames;
056        }
057        
058        public void setConstructorParameters(Class<?>[] constructorParameterTypes, Object[] constructorParameters) {              
059                
060                if (constructorParameterTypes == null)
061                        constructorParameterTypes = new Class<?>[0];
062                if (constructorParameters == null)
063                        constructorParameters = new Object[0];
064                
065                if (constructorParameterTypes.length != constructorParameters.length)
066                        throw new IllegalArgumentException("constructor types and argurments must have the same size");
067
068                this.constructorParameterTypes = constructorParameterTypes;
069                this.constructorParameters = constructorParameters;
070        }
071
072        public ServicesIterator<S> iterator() {
073                return new ServicesIterator<S>(loader, serviceClassNames.iterator(), constructorParameterTypes, constructorParameters);
074        }
075        
076        public void reload() {
077                ServiceLoader<S> serviceLoaderTmp = load(service, loader);
078                this.serviceClassNames = serviceLoaderTmp.serviceClassNames;
079        }
080        
081        public static <S> ServiceLoader<S> load(Class<S> service) {
082                return load(service, Thread.currentThread().getContextClassLoader());
083        }
084
085        public static <S> ServiceLoader<S> load(final Class<S> service, final ClassLoader loader) {
086                List<String> serviceClassNames = new ArrayList<String>();
087                
088                try {
089                        // Standard Java platforms.
090                        Enumeration<URL> en = loader.getResources("META-INF/services/" + service.getName());
091                        while (en.hasMoreElements())
092                                parse(en.nextElement(), serviceClassNames);
093                        
094                        // Android support (META-INF/** files are not included in APK files).
095                        en = loader.getResources("meta_inf/services/" + service.getName());
096                        while (en.hasMoreElements())
097                                parse(en.nextElement(), serviceClassNames);
098                        
099                        return new ServiceLoader<S>(service, loader, serviceClassNames);
100                }
101                catch (Exception e) {
102                        jdkLog.log(Level.SEVERE, "Could not load services of type " + service, e);
103                        throw new RuntimeException(e);
104                }
105        }
106        
107        private static void parse(URL serviceFile, List<String> serviceClassNames) {
108                try {
109                    InputStream is = serviceFile.openStream();
110                    try {
111                        BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
112        
113                        String serviceClassName = null;
114                        while ((serviceClassName = reader.readLine()) != null) {
115                                int comment = serviceClassName.indexOf('#');
116                                if (comment >= 0)
117                                        serviceClassName = serviceClassName.substring(0, comment);
118                                serviceClassName = serviceClassName.trim();
119                                if (serviceClassName.length() > 0) {
120                                        jdkLog.log(Level.FINE, "Adding service " + serviceClassName + " from " + serviceFile);
121                                        serviceClassNames.add(serviceClassName);
122                                }
123                        }
124                    }
125                    finally {
126                        is.close();
127                    }
128                }
129                catch (Exception e) {
130                        jdkLog.log(Level.SEVERE, "Could not parse service file " + serviceFile, e);
131                }
132        }
133        
134        public static class ServicesIterator<S> implements Iterator<S> {
135                
136                private final ClassLoader loader;
137                private final Iterator<String> serviceClassNames;
138                private final Class<?>[] constructorParameterTypes;
139                private final Object[] constructorParameters;
140
141                private ServicesIterator(ClassLoader loader, Iterator<String> servicesNames, Class<?>[] constructorParameterTypes, Object[] constructorParameters) {
142                        this.loader = loader;
143                        this.serviceClassNames = servicesNames;
144                        this.constructorParameterTypes = constructorParameterTypes;
145                        this.constructorParameters = constructorParameters;
146                }
147
148                public boolean hasNext() {
149                        return serviceClassNames.hasNext();
150                }
151
152                public S next() {
153                        String serviceClassName = serviceClassNames.next();
154                        jdkLog.log(Level.FINE, "Loading service " + serviceClassName);
155                        try {
156                                @SuppressWarnings("unchecked")
157                                Class<? extends S> serviceClass = (Class<? extends S>)loader.loadClass(serviceClassName);
158                                return serviceClass.getConstructor(constructorParameterTypes).newInstance(constructorParameters);
159                        }
160                        catch (Throwable t) {
161                                jdkLog.log(Level.SEVERE, "Could not load service " + serviceClassName, t);
162                                throw new RuntimeException(t);
163                        }
164                }
165
166                public void remove() {
167                        throw new UnsupportedOperationException();
168                }
169        }       
170}