/* * Copyright 2008 Ayman Al-Sairafi ayman.alsairafi@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License * at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package jsyntaxpane.util; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author subwiz * @author Ayman Al-Sairafi */ public class JarServiceProvider { public static final String SERVICES_ROOT = "META-INF/services/"; private static final Logger LOG = Logger.getLogger(JarServiceProvider.class.getName()); /** * Prevent anyone from instantiating this class. * Just use the static method */ private JarServiceProvider() { } private static ClassLoader getClassLoader() { ClassLoader cl = JarServiceProvider.class.getClassLoader(); return cl == null ? ClassLoader.getSystemClassLoader() : cl; } /** * Return an Object array from the file in META-INF/resources/{classname} * @param cls * @return * @throws java.io.IOException */ public static List<Object> getServiceProviders(Class cls) throws IOException { ArrayList<Object> l = new ArrayList<Object>(); ClassLoader cl = getClassLoader(); String serviceFile = SERVICES_ROOT + cls.getName(); Enumeration<URL> e = cl.getResources(serviceFile); while (e.hasMoreElements()) { URL u = e.nextElement(); InputStream is = u.openStream(); BufferedReader br = null; try { br = new BufferedReader( new InputStreamReader(is, Charset.forName("UTF-8"))); String str = null; while ((str = br.readLine()) != null) { int commentStartIdx = str.indexOf("#"); if (commentStartIdx != -1) { str = str.substring(0, commentStartIdx); } str = str.trim(); if (str.length() == 0) { continue; } try { Object obj = cl.loadClass(str).newInstance(); l.add(obj); } catch (Exception ex) { LOG.warning("Could not load: " + str); LOG.warning(ex.getMessage()); } } } finally { if (br != null) { br.close(); } } } return l; } /** * Read a file in the META-INF/services location. File name will be * fully qualified classname, in all lower-case, appended with ".properties" * If no file is found, then a an empty Property instance will be returned * @param clazz * @return Property file read. */ public static Properties readProperties(Class clazz) { return readProperties(clazz.getName()); } /** * Read a file in the META-INF/services named name appended with * ".properties" * * If no file is found, then a an empty Property instance will be returned * @param name name of file (use dots to separate subfolders). * @return Property file read. */ public static Properties readProperties(String name) { Properties props = new Properties(); String serviceFile = name.toLowerCase(); if (!serviceFile.endsWith(".properties")) { serviceFile += ".properties"; } InputStream is = findResource(serviceFile); if (is != null) { try { props.load(new InputStreamReader(is, "UTF-8")); } catch (IOException ex) { Logger.getLogger(JarServiceProvider.class.getName()).log(Level.SEVERE, null, ex); } } return props; } /** * Read language specific files in the META-INF/services named name appended * with ".properties". The contents of the files are merged as follows: * <ul> * <li>First the default language file (<name>.properties) is read</li> * <li>Then the general language variant of the file * (<name>_<lang>.properties) is read and its * entries are added to/overwrite the entries of the default life</li> * <li>Last the country specific language variant of the file * (<name>_<lang>_<country>.properties) is read and its * entries are added to/overwrite the existing entries</li> * </ul> * Example: You have three files: * <ul> * <li>config.properties which contains the complete configuration * (most likely with English menus, tooltips)</li> * <li>config_de.properties which only contains menu names and tooltips * in German language</li> * <li>config_de_CH which might just contain entries for specific * Swiss spelling variant of some words in a tooltip</li> * <ul> * * If no filesis found, then a an empty Property instance will be returned * @param name name of file (use dots to separate subfolders). * @param locale The locale for which to read the files * @return Property file read. */ public static Properties readProperties(String name, Locale locale) { // If name already ends in ".properties", then cut this off name = name.toLowerCase(); int idx = name.lastIndexOf(".properties"); if (idx > 0) { name = name.substring(0, idx); } // 1. Read properties of default langauge Properties props = readProperties(name); // 2. Read properties of general language variant if (locale != null && locale.getLanguage() != null) { name += "_"+locale.getLanguage(); Properties langProps = readProperties(name); props.putAll(langProps); } // 3. Read properties of country specific language variant if (locale != null && locale.getCountry() != null) { name += "_"+locale.getCountry(); Properties countryProps = readProperties(name); props.putAll(countryProps); } return props; } /** * Read a file in the META-INF/services named name appended with * ".properties", and returns it as a <code>Map<String, String></code> * If no file is found, then a an empty Property instance will be returned * @param name name of file (use dots to separate subfolders). * @return Map of keys and values */ public static Map<String, String> readStringsMap(String name) { Properties props = readProperties(name); HashMap<String, String> map = new HashMap<String, String>(); if (props != null) { for (Map.Entry e : props.entrySet()) { map.put(e.getKey().toString(), e.getValue().toString()); } } return map; } /** * Read the given URL and returns a List of Strings for each input line * Each line will not have the line terminator. * * The resource is searched in /META-INF/services/url, then in * url, then the url is treated as a location in the current classpath * and an attempt to read it from that location is done. * * @param url location of file to read * @return List of Strings for each line read. or EMPTY_LIST if URL is not found */ @SuppressWarnings("unchecked") public static List<String> readLines(String url) { InputStream is = findResource(url); if (is == null) { return Collections.EMPTY_LIST; } List<String> lines = new ArrayList<String>(); try { BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8")); for (String line = br.readLine(); line != null; line = br.readLine()) { // Trim and unescape some control chars line = line.trim().replace("\\n", "\n").replace("\\t", "\t"); lines.add(line); } } catch (IOException ex) { LOG.log(Level.SEVERE, null, ex); } finally { try { is.close(); } catch (IOException ex) { LOG.log(Level.SEVERE, null, ex); } return lines; } } /** * Attempt to find a location url. The following locations are searched in * sequence: * url, * SERVICES_ROOT/url * all classpath/url * @param url * @param cl classloader * @return InputSTream at that location, or null if not found * @see JarServiceProvider#findResource(java.lang.String) */ public static InputStream findResource(String url, ClassLoader cl) { InputStream is = null; URL loc = cl.getResource(url); if (loc == null) { loc = cl.getResource(url); } if (loc == null) { loc = cl.getResource(SERVICES_ROOT + url); } if (loc == null) { is = ClassLoader.getSystemResourceAsStream(url); } else { try { is = loc.openStream(); } catch (IOException ex) { Logger.getLogger(JarServiceProvider.class.getName()).log(Level.SEVERE, null, ex); } } return is; } /** * Attempt to find a location url. The following locations are searched in * sequence: * url, * SERVICES_ROOT/url * all classpath/url * The System ClassLoader is used. * @param url * @return InputSTream at that location, or null if not found * @see JarServiceProvider#findResource(java.lang.String, java.lang.ClassLoader) */ public static InputStream findResource(String url) { return findResource(url, getClassLoader()); } }