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