<commit_before>/**
 * Copyright (c) 2014-2016 by the respective copyright holders.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.eclipse.smarthome.core.voice;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.smarthome.core.audio.AudioFormat;
import org.eclipse.smarthome.core.audio.AudioSink;
import org.eclipse.smarthome.core.audio.AudioSource;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
import org.eclipse.smarthome.core.i18n.LocaleProvider;
import org.eclipse.smarthome.core.voice.internal.DialogProcessor;
import org.eclipse.smarthome.core.voice.text.HumanLanguageInterpreter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This service provides functionality around voice services and is the central service to be used directly by others.
 *
 * @author <NAME> - Initial contribution and API
 */
public class VoiceManager {

    // the default keyword to use if no other is configured
    private static final String DEFAULT_KEYWORD = "Wakeup";

    // constants for the configuration properties
    private static final String CONFIG_KEYWORD = "keyword";
    private static final String CONFIG_DEFAULT_HLI = "defaultHLI";
    private static final String CONFIG_DEFAULT_KS = "defaultKS";
    private static final String CONFIG_DEFAULT_STT = "defaultSTT";
    private static final String CONFIG_DEFAULT_TTS = "defaultTTS";
    private static final String CONFIG_DEFAULT_SINK = "defaultSink";
    private static final String CONFIG_DEFAULT_SOURCE = "defaultSource";
    private static final String CONFIG_PREFIX_DEFAULT_VOICE = "defaultVoice.";

    private final Logger logger = LoggerFactory.getLogger(VoiceManager.class);

    // service maps
    private Map<String, KSService> ksServices = new HashMap<>();
    private Map<String, STTService> sttServices = new HashMap<>();
    private Map<String, TTSService> ttsServices = new HashMap<>();
    private Map<String, HumanLanguageInterpreter> humanLanguageInterpreters = new HashMap<>();
    private Map<String, AudioSource> audioSources = new HashMap<>();
    private Map<String, AudioSink> audioSinks = new HashMap<>();

    private LocaleProvider localeProvider = null;

    /**
     * default settings filled through the service configuration
     */
    private String keyword = DEFAULT_KEYWORD;
    private String defaultSource = null;
    private String defaultSink = null;
    private String defaultTTS = null;
    private String defaultSTT = null;
    private String defaultKS = null;
    private String defaultHLI = null;
    private Map<String, String> defaultVoices = new HashMap<>();

    protected void activate(Map<String, Object> config) {
        modified(config);
    }

    protected void deactivate() {
    }

    protected void modified(Map<String, Object> config) {
        if (config != null) {
            this.keyword = config.containsKey(CONFIG_KEYWORD) ? config.get(CONFIG_KEYWORD).toString() : DEFAULT_KEYWORD;
            this.defaultSource = config.containsKey(CONFIG_DEFAULT_SOURCE)
                    ? config.get(CONFIG_DEFAULT_SOURCE).toString() : null;
            this.defaultSink = config.containsKey(CONFIG_DEFAULT_SINK) ? config.get(CONFIG_DEFAULT_SINK).toString()
                    : null;
            this.defaultTTS = config.containsKey(CONFIG_DEFAULT_TTS) ? config.get(CONFIG_DEFAULT_TTS).toString() : null;
            this.defaultSTT = config.containsKey(CONFIG_DEFAULT_STT) ? config.get(CONFIG_DEFAULT_STT).toString() : null;
            this.defaultKS = config.containsKey(CONFIG_DEFAULT_KS) ? config.get(CONFIG_DEFAULT_KS).toString() : null;
            this.defaultHLI = config.containsKey(CONFIG_DEFAULT_HLI) ? config.get(CONFIG_DEFAULT_HLI).toString() : null;

            for (String key : config.keySet()) {
                if (key.startsWith(CONFIG_PREFIX_DEFAULT_VOICE)) {
                    String tts = key.substring(CONFIG_PREFIX_DEFAULT_VOICE.length());
                    defaultVoices.put(tts, config.get(key).toString());
                }
            }
        }
    }

    /**
     * Speaks the passed string using the default TTS service and default audio sink.
     *
     * @param text The text to say
     */
    public void say(String text) {
        say(text, null);
    }

    /**
     * Speaks the passed string using the provided voiceId and the default audio sink.
     * If the voiceId is fully qualified (i.e. with a tts prefix), the according TTS service will be used, otherwise the
     * voiceId is assumed to be available on the default TTS service.
     *
     * @param text The text to say
     * @param voiceId The id of the voice to use (either with or without prefix)
     */
    public void say(String text, String voiceId) {
        say(text, voiceId, null);
    }

    /**
     * Speaks the passed string using the provided voiceId and the given audio sink.
     * If the voiceId is fully qualified (i.e. with a tts prefix), the according TTS service will be used, otherwise the
     * voiceId is assumed to be available on the default TTS service.
     *
     * @param text The text to say
     * @param voiceId The id of the voice to use (either with or without prefix) or null
     * @param sinkId The id of the audio sink to use or null
     */
    public void say(String text, String voiceId, String sinkId) {
        try {
            TTSService tts = null;
            Voice voice = null;
            if (voiceId == null) {
                tts = getTTS();
                voice = getPreferredVoice(tts.getAvailableVoices());
            } else if (voiceId.contains(":")) {
                // it is a fully qualified unique id
                String[] segments = voiceId.split(":");
                tts = ttsServices.get(segments[0]);
                voice = getVoice(tts.getAvailableVoices(), segments[1]);
            } else {
                // voiceId is not fully qualified
                tts = getTTS();
                voice = getVoice(tts.getAvailableVoices(), voiceId);
            }<commit_msg>avoid NPE if default TTS cannot be found (#2080)<commit_after>/**
 * Copyright (c) 2014-2016 by the respective copyright holders.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 */
package org.eclipse.smarthome.core.voice;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.eclipse.smarthome.core.audio.AudioFormat;
import org.eclipse.smarthome.core.audio.AudioSink;
import org.eclipse.smarthome.core.audio.AudioSource;
import org.eclipse.smarthome.core.audio.AudioStream;
import org.eclipse.smarthome.core.audio.UnsupportedAudioFormatException;
import org.eclipse.smarthome.core.i18n.LocaleProvider;
import org.eclipse.smarthome.core.voice.internal.DialogProcessor;
import org.eclipse.smarthome.core.voice.text.HumanLanguageInterpreter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This service provides functionality around voice services and is the central service to be used directly by others.
 *
 * @author <NAME> - Initial contribution and API
 */
public class VoiceManager {

    // the default keyword to use if no other is configured
    private static final String DEFAULT_KEYWORD = "Wakeup";

    // constants for the configuration properties
    private static final String CONFIG_KEYWORD = "keyword";
    private static final String CONFIG_DEFAULT_HLI = "defaultHLI";
    private static final String CONFIG_DEFAULT_KS = "defaultKS";
    private static final String CONFIG_DEFAULT_STT = "defaultSTT";
    private static final String CONFIG_DEFAULT_TTS = "defaultTTS";
    private static final String CONFIG_DEFAULT_SINK = "defaultSink";
    private static final String CONFIG_DEFAULT_SOURCE = "defaultSource";
    private static final String CONFIG_PREFIX_DEFAULT_VOICE = "defaultVoice.";

    private final Logger logger = LoggerFactory.getLogger(VoiceManager.class);

    // service maps
    private Map<String, KSService> ksServices = new HashMap<>();
    private Map<String, STTService> sttServices = new HashMap<>();
    private Map<String, TTSService> ttsServices = new HashMap<>();
    private Map<String, HumanLanguageInterpreter> humanLanguageInterpreters = new HashMap<>();
    private Map<String, AudioSource> audioSources = new HashMap<>();
    private Map<String, AudioSink> audioSinks = new HashMap<>();

    private LocaleProvider localeProvider = null;

    /**
     * default settings filled through the service configuration
     */
    private String keyword = DEFAULT_KEYWORD;
    private String defaultSource = null;
    private String defaultSink = null;
    private String defaultTTS = null;
    private String defaultSTT = null;
    private String defaultKS = null;
    private String defaultHLI = null;
    private Map<String, String> defaultVoices = new HashMap<>();

    protected void activate(Map<String, Object> config) {
        modified(config);
    }

    protected void deactivate() {
    }

    protected void modified(Map<String, Object> config) {
        if (config != null) {
            this.keyword = config.containsKey(CONFIG_KEYWORD) ? config.get(CONFIG_KEYWORD).toString() : DEFAULT_KEYWORD;
            this.defaultSource = config.containsKey(CONFIG_DEFAULT_SOURCE)
                    ? config.get(CONFIG_DEFAULT_SOURCE).toString() : null;
            this.defaultSink = config.containsKey(CONFIG_DEFAULT_SINK) ? config.get(CONFIG_DEFAULT_SINK).toString()
                    : null;
            this.defaultTTS = config.containsKey(CONFIG_DEFAULT_TTS) ? config.get(CONFIG_DEFAULT_TTS).toString() : null;
            this.defaultSTT = config.containsKey(CONFIG_DEFAULT_STT) ? config.get(CONFIG_DEFAULT_STT).toString() : null;
            this.defaultKS = config.containsKey(CONFIG_DEFAULT_KS) ? config.get(CONFIG_DEFAULT_KS).toString() : null;
            this.defaultHLI = config.containsKey(CONFIG_DEFAULT_HLI) ? config.get(CONFIG_DEFAULT_HLI).toString() : null;

            for (String key : config.keySet()) {
                if (key.startsWith(CONFIG_PREFIX_DEFAULT_VOICE)) {
                    String tts = key.substring(CONFIG_PREFIX_DEFAULT_VOICE.length());
                    defaultVoices.put(tts, config.get(key).toString());
                }
            }
        }
    }

    /**
     * Speaks the passed string using the default TTS service and default audio sink.
     *
     * @param text The text to say
     */
    public void say(String text) {
        say(text, null);
    }

    /**
     * Speaks the passed string using the provided voiceId and the default audio sink.
     * If the voiceId is fully qualified (i.e. with a tts prefix), the according TTS service will be used, otherwise the
     * voiceId is assumed to be available on the default TTS service.
     *
     * @param text The text to say
     * @param voiceId The id of the voice to use (either with or without prefix)
     */
    public void say(String text, String voiceId) {
        say(text, voiceId, null);
    }

    /**
     * Speaks the passed string using the provided voiceId and the given audio sink.
     * If the voiceId is fully qualified (i.e. with a tts prefix), the according TTS service will be used, otherwise the
     * voiceId is assumed to be available on the default TTS service.
     *
     * @param text The text to say
     * @param voiceId The id of the voice to use (either with or without prefix) or null
     * @param sinkId The id of the audio sink to use or null
     */
    public void say(String text, String voiceId, String sinkId) {
        try {
            TTSService tts = null;
            Voice voice = null;
            if (voiceId == null) {
                tts = getTTS();
                if (tts != null) {
                    voice = getPreferredVoice(tts.getAvailableVoices());
                }
            } else if (voiceId.contains(":")) {
                // it is a fully qualified unique id
                String[] segments = voiceId.split(":");
                tts = ttsServices.get(segments[0]);
                voice = getVoice(tts.getAvailableVoices(), segments[1]);
            } else {
                // voiceId is not fully qualified
                tts = getTTS();
                voice = getVoice(tts.getAvailableVoices(), voiceId);
            }
{'fraction_non_alphanumeric': 0.08970792767732963, 'fraction_numerical': 0.003090712409210323, 'mean_word_length': 2.96416539050536, 'pattern_counts': {'":': 4, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4160104', 'n_tokens_mistral': 3529, 'n_tokens_neox': 3440, 'n_words': 1096}