/************************************************************************** OmegaT - Computer Assisted Translation (CAT) tool with fuzzy matching, translation memory, keyword search, glossaries, and translation leveraging into updated projects. Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk 2010 Alex Buloichik Home page: http://www.omegat.org/ Support center: http://groups.yahoo.com/group/OmegaT/ This file is part of OmegaT. OmegaT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OmegaT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. **************************************************************************/ package org.omegat.filters2.master; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.InputStream; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.stream.Collectors; import java.util.stream.Stream; import org.omegat.CLIParameters; import org.omegat.core.Core; import org.omegat.tokenizer.DefaultTokenizer; import org.omegat.tokenizer.ITokenizer; import org.omegat.tokenizer.Tokenizer; import org.omegat.util.FileUtil; import org.omegat.util.Language; import org.omegat.util.Log; import org.omegat.util.OStrings; import org.omegat.util.StaticUtils; import org.omegat.util.StringUtil; /** * Static utilities for OmegaT filter plugins. * * @author Maxym Mykhalchuk * @author Alex Buloichik (alex73mail@gmail.com) */ public final class PluginUtils { public static final String PLUGINS_LIST_FILE = "Plugins.properties"; enum PluginType { FILTER, TOKENIZER, MARKER, MACHINETRANSLATOR, BASE, GLOSSARY, UNKNOWN }; protected static final List<Class<?>> LOADED_PLUGINS = new ArrayList<Class<?>>(); /** Private constructor to disallow creation */ private PluginUtils() { } /** * Loads all plugins from main classloader and from /plugins/ dir. We should * load all jars from /plugins/ dir first, because some plugin can use more * than one jar. */ public static void loadPlugins(final Map<String, String> params) { File pluginsDir = new File(StaticUtils.installDir(), "plugins"); File homePluginsDir = new File(StaticUtils.getConfigDir(), "plugins"); try { // list all jars in /plugins/ FileFilter jarFilter = pathname -> pathname.getName().endsWith(".jar"); List<File> fs = Stream.of(pluginsDir, homePluginsDir) .flatMap(dir -> FileUtil.findFiles(dir, jarFilter).stream()) .collect(Collectors.toList()); URL[] urls = new URL[fs.size()]; for (int i = 0; i < urls.length; i++) { urls[i] = fs.get(i).toURI().toURL(); Log.logInfoRB("PLUGIN_LOAD_JAR", urls[i].toString()); } boolean foundMain = false; // look on all manifests URLClassLoader pluginsClassLoader = new URLClassLoader(urls, PluginUtils.class.getClassLoader()); for (Enumeration<URL> mlist = pluginsClassLoader.getResources("META-INF/MANIFEST.MF"); mlist .hasMoreElements();) { URL mu = mlist.nextElement(); try (InputStream in = mu.openStream()) { Manifest m = new Manifest(in); if ("org.omegat.Main".equals(m.getMainAttributes().getValue("Main-Class"))) { // found main manifest - not in development mode foundMain = true; } loadFromManifest(m, pluginsClassLoader); } } if (!foundMain) { // development mode - load from dev-manifests CLI arg String manifests = params.get(CLIParameters.DEV_MANIFESTS); if (manifests != null) { for (String mf : manifests.split(File.pathSeparator)) { try (InputStream in = new FileInputStream(mf)) { loadFromManifest(new Manifest(in), pluginsClassLoader); } } } else { // load from plugins property list Properties props = new Properties(); try (FileInputStream fis = new FileInputStream(PLUGINS_LIST_FILE)) { props.load(fis); loadFromProperties(props, pluginsClassLoader); } } } } catch (Exception ex) { Log.log(ex); } // run base plugins for (Class<?> pl : BASE_PLUGIN_CLASSES) { try { pl.newInstance(); } catch (Exception ex) { Log.log(ex); } } } public static List<Class<?>> getFilterClasses() { return FILTER_CLASSES; } public static List<Class<?>> getTokenizerClasses() { return TOKENIZER_CLASSES; } public static Class<?> getTokenizerClassForLanguage(Language lang) { if (lang == null) { return DefaultTokenizer.class; } // Prefer an exact match on the full ISO language code (XX-YY). Class<?> exactResult = searchForTokenizer(lang.getLanguage()); if (isDefault(exactResult)) { return exactResult; } // Otherwise return a match for the language only (XX). Class<?> generalResult = searchForTokenizer(lang.getLanguageCode()); if (isDefault(generalResult)) { return generalResult; } else if (exactResult != null) { return exactResult; } else if (generalResult != null) { return generalResult; } return DefaultTokenizer.class; } private static boolean isDefault(Class<?> c) { if (c == null) { return false; } Tokenizer ann = c.getAnnotation(Tokenizer.class); return ann == null ? false : ann.isDefault(); } private static Class<?> searchForTokenizer(String lang) { if (lang.isEmpty()) { return null; } lang = lang.toLowerCase(); // Choose first relevant tokenizer as fallback if no // "default" tokenizer is found. Class<?> fallback = null; for (Class<?> c : TOKENIZER_CLASSES) { Tokenizer ann = c.getAnnotation(Tokenizer.class); if (ann == null) { continue; } String[] languages = ann.languages(); try { if (languages.length == 1 && languages[0].equals(Tokenizer.DISCOVER_AT_RUNTIME)) { languages = ((ITokenizer) c.newInstance()).getSupportedLanguages(); } } catch (Exception ex) { // Nothing } for (String s : languages) { if (lang.equals(s)) { if (ann.isDefault()) { return c; // Return best possible match. } else if (fallback == null) { fallback = c; } } } } return fallback; } public static List<Class<?>> getMarkerClasses() { return MARKER_CLASSES; } public static List<Class<?>> getMachineTranslationClasses() { return MACHINE_TRANSLATION_CLASSES; } public static List<Class<?>> getGlossaryClasses() { return GLOSSARY_CLASSES; } protected static final List<Class<?>> FILTER_CLASSES = new ArrayList<>(); protected static final List<Class<?>> TOKENIZER_CLASSES = new ArrayList<>(); protected static final List<Class<?>> MARKER_CLASSES = new ArrayList<>(); protected static final List<Class<?>> MACHINE_TRANSLATION_CLASSES = new ArrayList<>(); protected static final List<Class<?>> GLOSSARY_CLASSES = new ArrayList<>(); protected static final List<Class<?>> BASE_PLUGIN_CLASSES = new ArrayList<>(); /** * Parse one manifest file. * * @param m * manifest * @param classLoader * classloader * @throws ClassNotFoundException */ protected static void loadFromManifest(final Manifest m, final ClassLoader classLoader) throws ClassNotFoundException { String pluginClasses = m.getMainAttributes().getValue("OmegaT-Plugins"); if (pluginClasses != null) { for (String clazz : pluginClasses.split("\\s+")) { if (clazz.trim().isEmpty()) { continue; } loadClass(clazz, classLoader); } } loadFromManifestOld(m, classLoader); } protected static void loadFromProperties(Properties props, ClassLoader classLoader) throws ClassNotFoundException { for (Object o : props.keySet()) { String key = o.toString(); String[] classes = props.getProperty(key).split("\\s+"); if (key.equals("plugin")) { for (String clazz : classes) { loadClass(clazz, classLoader); } } else { for (String clazz : classes) { loadClassOld(key, clazz, classLoader); } } } } protected static void loadClass(String clazz, ClassLoader classLoader) { try { Class<?> c = classLoader.loadClass(clazz); Method load = c.getMethod("loadPlugins"); load.invoke(c); LOADED_PLUGINS.add(c); Log.logInfoRB("PLUGIN_LOAD_OK", clazz); } catch (Throwable ex) { Log.logErrorRB(ex, "PLUGIN_LOAD_ERROR", clazz, ex.getClass().getSimpleName(), ex.getMessage()); Core.pluginLoadingError(StringUtil.format(OStrings.getString("PLUGIN_LOAD_ERROR"), clazz, ex.getClass().getSimpleName(), ex.getMessage())); } } public static void unloadPlugins() { for (Class<?> p : LOADED_PLUGINS) { try { Method load = p.getMethod("unloadPlugins"); load.invoke(p); } catch (Throwable ex) { Log.logErrorRB(ex, "PLUGIN_UNLOAD_ERROR", p.getClass().getSimpleName(), ex.getMessage()); } } } /** * Old-style plugin loading. */ protected static void loadFromManifestOld(final Manifest m, final ClassLoader classLoader) throws ClassNotFoundException { if (m.getMainAttributes().getValue("OmegaT-Plugin") == null) { return; } Map<String, Attributes> entries = m.getEntries(); for (Entry<String, Attributes> e : entries.entrySet()) { String key = e.getKey(); Attributes attrs = e.getValue(); String sType = attrs.getValue("OmegaT-Plugin"); if ("true".equals(attrs.getValue("OmegaT-Tokenizer"))) { // TODO remove after release new tokenizers sType = "tokenizer"; } if (sType == null) { // WebStart signing section, or other section continue; } loadClassOld(sType, key, classLoader); } } protected static void loadClassOld(String sType, String key, ClassLoader classLoader) throws ClassNotFoundException { PluginType pType; try { pType = PluginType.valueOf(sType.toUpperCase(Locale.ENGLISH)); } catch (Exception ex) { pType = PluginType.UNKNOWN; } switch (pType) { case FILTER: FILTER_CLASSES.add(classLoader.loadClass(key)); Log.logInfoRB("PLUGIN_LOAD_OK", key); break; case TOKENIZER: TOKENIZER_CLASSES.add(classLoader.loadClass(key)); Log.logInfoRB("PLUGIN_LOAD_OK", key); break; case MARKER: MARKER_CLASSES.add(classLoader.loadClass(key)); Log.logInfoRB("PLUGIN_LOAD_OK", key); break; case MACHINETRANSLATOR: MACHINE_TRANSLATION_CLASSES.add(classLoader.loadClass(key)); Log.logInfoRB("PLUGIN_LOAD_OK", key); break; case BASE: BASE_PLUGIN_CLASSES.add(classLoader.loadClass(key)); Log.logInfoRB("PLUGIN_LOAD_OK", key); break; case GLOSSARY: GLOSSARY_CLASSES.add(classLoader.loadClass(key)); Log.logInfoRB("PLUGIN_LOAD_OK", key); break; default: Log.logErrorRB("PLUGIN_UNKNOWN", sType, key); } } }