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     */
022    package org.granite.scan;
023    
024    import java.io.BufferedReader;
025    import java.io.InputStream;
026    import java.io.InputStreamReader;
027    import java.net.URL;
028    import java.util.ArrayList;
029    import java.util.Enumeration;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.logging.Level;
033    import java.util.logging.Logger;
034    
035    /**
036     * @author Franck WOLFF
037     */
038    public 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    }