Java tutorial
/** * Copyright 2008 DFKI GmbH. * All Rights Reserved. Use is subject to license terms. * * This file is part of MARY TTS. * * MARY TTS is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ package marytts.modules; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import marytts.datatypes.MaryDataType; import marytts.exceptions.MaryConfigurationException; import marytts.modules.synthesis.Voice; import marytts.util.MaryRuntimeUtils; import marytts.util.MaryUtils; import org.apache.commons.collections.map.MultiKeyMap; import org.apache.log4j.Logger; /** * A hierarchical repository for Mary modules, * allowing the flexible indexing by an ordered hierarchy of datatype, locale and voice. * A given lookup will search for a combination of datatype, locale and voice * first; if it does not find a value, it will look for datatype, locale, and null; * if it does notfind that, it will look for datatype, null, and null. * * @author marc * */ public class ModuleRegistry { private static MultiKeyMap mkm; private static List<MaryModule> allModules; private static boolean registrationComplete; private static Logger logger; private ModuleRegistry() { } /** * Create a new, empty module repository. */ static { mkm = new MultiKeyMap(); allModules = new LinkedList<MaryModule>(); registrationComplete = false; logger = MaryUtils.getLogger("ModuleRegistry"); } ////////////////////////////////////////////////////////////////// ///////////////////////// instantiation ////////////////////////// ////////////////////////////////////////////////////////////////// /** * From the given module init info, instantiate a new mary module. * @param moduleInitInfo a string description of the module to instantiate. * The moduleInitInfo is expected to have one of the following forms: * <ol> * <li> my.class.which.extends.MaryModule</li> * <li> my.class.which.extends.MaryModule(any,string,args,without,spaces)</li> * <li>my.class.which.extends.MaryModule(arguments,$my.special.property,other,args)</li> * </ol> * where 'my.special.property' is a property in the property file. * @throws MaryConfigurationException if the module cannot be instantiated */ public static MaryModule instantiateModule(String moduleInitInfo) throws MaryConfigurationException { logger.info("Now initiating mary module '" + moduleInitInfo + "'"); MaryModule m = (MaryModule) MaryRuntimeUtils.instantiateObject(moduleInitInfo); return m; } ////////////////////////////////////////////////////////////////// ///////////////////////// registration /////////////////////////// ////////////////////////////////////////////////////////////////// /** * Register a MaryModule as an appropriate module to process the given * combination of MaryDataType for the input data, locale of the input data, * and voice requested for processing. * Note that it is possible to register more than one module for a given * combination of input type, locale and voice; in that case, all of them * will be remembered, and will be returned as a List<MaryModule> by get(). * * @param module the module to add to the registry, under its input type and the given locale and voice. * @param locale the locale (language or language-COUNTRY) of the input data; can be null to signal that * the module is locale-independent. * @param voice a voice for which this module is suited. Can be null to indicate * that the module is not specific to any voice. * @throws IllegalStateException if called after registration is complete. */ @SuppressWarnings("unchecked") public static void registerModule(MaryModule module, Locale locale, Voice voice) throws IllegalStateException { if (registrationComplete) throw new IllegalStateException("cannot register modules after registration is complete"); MaryDataType type = module.inputType(); Object o = mkm.get(type, locale, voice); List<MaryModule> l; if (o != null) { assert o instanceof List : "Expected List of MaryModules, got " + o.getClass(); l = (List<MaryModule>) o; } else { l = new ArrayList<MaryModule>(1); mkm.put(type, locale, voice, l); } assert l != null; l.add(module); allModules.add(module); MaryDataType.registerDataType(type); MaryDataType.registerDataType(module.outputType()); } /** * Determine whether or not the registration is complete. * When the registration is not (yet) complete, calls to * @see #registerModule(MaryModule, Locale, Voice) are possible; * when the registration is complete, calls to the other methods are possible. * * @return false when the registration is still open, true when it is complete. */ public static boolean getRegistrationComplete() { return registrationComplete; } /** * Indicate that the registration is now complete. No further calls to * registerModules() will be possible. * @throws IllegalStateException if called when registration was already completed before. */ public static void setRegistrationComplete() throws IllegalStateException { if (registrationComplete) throw new IllegalStateException("Registration has already completed, cannot do that a second time"); registrationComplete = true; MaryDataType.setRegistrationComplete(); } ////////////////////////////////////////////////////////////////// ///////////////////////// modules ///////////////////////////// ////////////////////////////////////////////////////////////////// /** * Provide a list containing all MaryModules instances. * The order is not important. * @throws IllegalStateException if called while registration is not yet complete. * */ public static List<MaryModule> getAllModules() { if (!registrationComplete) throw new IllegalStateException("Cannot inquire about modules while registration is ongoing"); return Collections.unmodifiableList(allModules); } /** * Find an active module by its class. * @return the module instance if found, or null if not found. * @throws IllegalStateException if called while registration is not yet complete. */ // TODO: what should happen with this method when we parameterise modules, so that there can be several instances of the same class? public static MaryModule getModule(Class<?> moduleClass) { if (!registrationComplete) throw new IllegalStateException("Cannot inquire about modules while registration is ongoing"); for (Iterator<MaryModule> it = allModules.iterator(); it.hasNext();) { MaryModule m = it.next(); if (m.getClass().equals(moduleClass)) { return m; } } // Not found: return null; } /** * A method for determining the list of modules required to transform * the given source data type into the requested target data type. * @return the (ordered) list of modules required, or null if no such * list could be found. * @throws IllegalStateException if called while registration is not yet complete. * @throws NullPointerException if source data type, target data type or locale is null. */ public static LinkedList<MaryModule> modulesRequiredForProcessing(MaryDataType sourceType, MaryDataType targetType, Locale locale) { return modulesRequiredForProcessing(sourceType, targetType, locale, null); } /** * A method for determining the list of modules required to transform * the given source data type into the requested target data type. If the * voice given is not null, any preferred modules it may have are taken into account. * @return the (ordered) list of modules required, or null if no such * list could be found. * @throws IllegalStateException if called while registration is not yet complete. * @throws NullPointerException if source data type, target data type or locale is null. */ public static LinkedList<MaryModule> modulesRequiredForProcessing(MaryDataType sourceType, MaryDataType targetType, Locale locale, Voice voice) { if (!registrationComplete) throw new IllegalStateException("Cannot inquire about modules while registration is ongoing"); if (sourceType == null) throw new NullPointerException("Received null source type"); if (targetType == null) throw new NullPointerException("Received null target type"); //if (locale == null) // throw new NullPointerException("Received null locale"); LinkedList<MaryDataType> seenTypes = new LinkedList<MaryDataType>(); seenTypes.add(sourceType); return modulesRequiredForProcessing(sourceType, targetType, locale, voice, seenTypes); } /** * This method recursively calls itself. * It forward-constructs a list of seen types (upon test), * and backward-constructs a list of required modules (upon success). * * @param sourceType * @param targetType * @param locale * @param voice * @param seenTypes * @return */ private static LinkedList<MaryModule> modulesRequiredForProcessing(MaryDataType sourceType, MaryDataType targetType, Locale locale, Voice voice, LinkedList<MaryDataType> seenTypes) { // Terminating condition: if (sourceType.equals(targetType)) { logger.debug("found path through modules"); return new LinkedList<MaryModule>(); } // Recursion step: // Any voice-specific modules? List<MaryModule> candidates = null; // TODO: the following should be obsolete as soon as we are properly using the voice index in ModuleRegistry if (voice != null) candidates = voice.getPreferredModulesAcceptingType(sourceType); if (candidates == null || candidates.isEmpty()) { // default: use all available modules candidates = get(sourceType, locale, voice); } if (candidates == null || candidates.isEmpty()) { // no module can handle this type return null; } for (Iterator<MaryModule> it = candidates.iterator(); it.hasNext();) { MaryModule candidate = it.next(); MaryDataType outputType = candidate.outputType(); // Ignore candidates that would bring us to a data type that we // have already seen (i.e., that would lead to a loop): if (!seenTypes.contains(outputType)) { seenTypes.add(outputType); logger.debug("Module " + candidate.name() + " converts " + sourceType.name() + " into " + outputType + " (locale " + locale + ", voice " + voice + ")"); // recursive call: LinkedList<MaryModule> path = modulesRequiredForProcessing(outputType, targetType, locale, voice, seenTypes); if (path != null) { // success, found a path of which candidate is the first // step path.addFirst(candidate); return path; } // else, try next candidate seenTypes.removeLast(); } } // We get here only if none of the candidates lead to a valid path return null; // failure } /** * Lookup a list of modules for the given combination of type, locale and voice. * A given lookup will return a list of modules accepting the datatype as input, * ordered by specificity so that the most specific modules come first. * For each output type produced by modules, there will be only one module * producing that type, namely the most specific one found. * Specificity is ordered as follows: * 1. most specific is the full combination of datatype, locale and voice; * 2. the combination of datatype, language-only locale and voice (i.e., for requested locale "en-US" will find modules with locale "en"); * 3. the combination of datatype and locale, ignoring voice; * 4. the combination of datatype and language-only locale, ignoring voice; * 5. least specific is the datatype, ignoring locale and voice. * @param type the type of input data * @param locale the locale * @param voice the voice to use. * @return a list of mary modules accepting the datatype and suitable for * the given locale and voice, ordered by their specificity, * or an empty list if there is no suitable module. * @throws IllegalStateException if called when registration is not yet complete. */ @SuppressWarnings("unchecked") private static List<MaryModule> get(MaryDataType type, Locale locale, Voice voice) throws IllegalStateException { if (!registrationComplete) throw new IllegalStateException("Cannot inquire about modules while registration is ongoing"); LinkedHashMap<MaryDataType, MaryModule> results = new LinkedHashMap<MaryDataType, MaryModule>(); // First, get all results: List<List<MaryModule>> listOfLists = new LinkedList<List<MaryModule>>(); listOfLists.add((List<MaryModule>) mkm.get(type, locale, voice)); Locale langOnly = locale != null ? new Locale(locale.getLanguage()) : null; boolean haveCountry = langOnly == null ? false : !langOnly.equals(locale); if (haveCountry) { listOfLists.add((List<MaryModule>) mkm.get(type, langOnly, voice)); } listOfLists.add((List<MaryModule>) mkm.get(type, locale, null)); if (haveCountry) { listOfLists.add((List<MaryModule>) mkm.get(type, langOnly, null)); } listOfLists.add((List<MaryModule>) mkm.get(type, null, null)); // Now, for each mary output type, keep only the most specific module, // and return the list ordered by the specificity of modules (most specific first): for (List<MaryModule> list : listOfLists) { if (list != null) { for (MaryModule m : list) { if (!results.containsKey(m.outputType())) results.put(m.outputType(), m); } } } List<MaryModule> returnList = new LinkedList<MaryModule>(); for (MaryDataType t : results.keySet()) { returnList.add(results.get(t)); } return returnList; } }