[389790] multiple language id support
diff --git a/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/MspVoice.java b/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/MspVoice.java
index 5415010..9129ade 100644
--- a/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/MspVoice.java
+++ b/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/MspVoice.java
@@ -12,13 +12,20 @@
 package org.eclipse.actf.ai.tts.msp.engine;
 
 import java.io.File;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
 
 import org.eclipse.actf.ai.tts.ISAPIEngine;
+import org.eclipse.actf.ai.tts.ITTSEngineInfo;
 import org.eclipse.actf.ai.tts.msp.MspPlugin;
 import org.eclipse.actf.ai.voice.IVoiceEventListener;
 import org.eclipse.actf.util.win32.COMUtil;
 import org.eclipse.actf.util.win32.MemoryUtil;
 import org.eclipse.actf.util.win32.NativeIntAccess;
+import org.eclipse.core.runtime.Platform;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
@@ -51,6 +58,42 @@
 
 	private SpObjectToken curVoiceToken = null;
 
+	private class EngineInfo implements ITTSEngineInfo {
+		String name;
+		String lang;
+		String langId;
+		String gender;
+
+		public EngineInfo(String name, String lang, String langId, String gender) {
+			this.name = name;
+			this.lang = lang;
+			this.langId = langId;
+			this.gender = gender;
+		}
+
+		public String getName() {
+			return name;
+		}
+
+		public String getLanguage() {
+			return lang;
+		}
+
+		public String getGender() {
+			return gender;
+		}
+
+	}
+
+	private Map<String, TreeSet<EngineInfo>> langId2EngineMap = new HashMap<String, TreeSet<EngineInfo>>();
+	private Set<ITTSEngineInfo> ttsEngineInfoSet = new TreeSet<ITTSEngineInfo>(
+			new Comparator<ITTSEngineInfo>() {
+				public int compare(ITTSEngineInfo o1, ITTSEngineInfo o2) {
+					// TODO null, lang/gender check
+					return o1.getName().compareTo(o2.getName());
+				}
+			});
+	
 	public MspVoice() {
 		int pv = COMUtil.createDispatch(ISpVoice.IID);
 		dispSpVoice = new ISpVoice(pv);
@@ -68,7 +111,57 @@
 		setAudioOutputName();
 		// switch to actual engine
 		preferenceStore.setValue(ID, orgID);
-		setVoiceName(); //for init curVoiceToken
+		setVoiceName(); // for init curVoiceToken
+
+		Variant varVoices = getVoices(null, null);
+		if (null != varVoices) {
+			SpeechObjectTokens voiceTokens = SpeechObjectTokens
+					.getTokens(varVoices);
+			if (null != voiceTokens) {
+				String exclude = Platform.getResourceString(MspPlugin
+						.getDefault().getBundle(), "%voice.exclude"); //$NON-NLS-1$
+				int count = voiceTokens.getCount();
+				for (int i = 0; i < count; i++) {
+					Variant varVoice = voiceTokens.getItem(i);
+					if (null != varVoice) {
+						SpObjectToken token = SpObjectToken.getToken(varVoice);
+						if (null != token) {
+							String voiceName = token.getDescription(0);
+							String langId = token.getAttribute("language"); //$NON-NLS-1$
+							int index = langId.indexOf(";");
+							// use primary lang ID
+							if (index > 0) {
+								langId = langId.substring(0, index);
+							}
+							String gender = token.getAttribute("gender"); //$NON-NLS-1$
+							if (null == exclude || !exclude.equals(voiceName)) {
+								TreeSet<EngineInfo> set = langId2EngineMap
+										.get(langId);
+								if (set == null) {
+									set = new TreeSet<EngineInfo>(
+											new Comparator<EngineInfo>() {
+												public int compare(
+														EngineInfo o1,
+														EngineInfo o2) {
+													// TODO priority
+													return -o1.name
+															.compareTo(o2.name);
+												}
+											});
+									langId2EngineMap.put(langId, set);
+								}
+								String lang = LANGID_REVERSE_MAP.get(langId);
+								EngineInfo engineInfo = new EngineInfo(
+										voiceName, lang, langId, gender);
+								set.add(engineInfo);
+								ttsEngineInfoSet.add(engineInfo);
+							}
+						}
+					}
+				}
+			}
+			varVoices.dispose();
+		}
 
 		// to avoid access violation error at application shutdown
 		stop();
@@ -415,6 +508,9 @@
 	 * @see org.eclipse.actf.ai.tts.ITTSEngine#setGender(java.lang.String)
 	 */
 	public void setGender(String gender) {
+		if (gender == null) {
+			return;
+		}
 		String langId = null;
 		if (curVoiceToken != null) {
 			langId = curVoiceToken.getAttribute("language");
@@ -485,7 +581,7 @@
 			// open 100 close 101
 			String tmpS = file.toURI().toString();
 			if (tmpS.startsWith("file:/")) {
-				tmpS = tmpS.substring(6).replaceAll("%20"," ");;
+				tmpS = tmpS.substring(6).replaceAll("%20", " ");
 			}
 
 			autoSpFileStream.invoke(100, new Variant[] { new Variant(tmpS),
@@ -516,4 +612,9 @@
 		setAudioOutputName(); // reset output
 		return speakToFileResult;
 	}
+
+	public Set<ITTSEngineInfo> getTTSEngineInfoSet() {
+		return ttsEngineInfoSet;
+	}
+
 }
diff --git a/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SapiVoice.java b/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SapiVoice.java
index 38635a8..7ead32b 100644
--- a/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SapiVoice.java
+++ b/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SapiVoice.java
@@ -15,9 +15,11 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeSet;
 
 import org.eclipse.actf.ai.tts.ISAPIEngine;
+import org.eclipse.actf.ai.tts.ITTSEngineInfo;
 import org.eclipse.actf.ai.tts.sapi.SAPIPlugin;
 import org.eclipse.actf.ai.voice.IVoiceEventListener;
 import org.eclipse.actf.util.win32.COMUtil;
@@ -56,19 +58,41 @@
 
 	private SpObjectToken curVoiceToken = null;
 
-	private class EngineInfo {
+	private class EngineInfo implements ITTSEngineInfo {
 		String name;
+		String lang;
 		String langId;
 		String gender;
 
-		public EngineInfo(String name, String langId, String gender) {
+		public EngineInfo(String name, String lang, String langId, String gender) {
 			this.name = name;
+			this.lang = lang;
 			this.langId = langId;
 			this.gender = gender;
 		}
+
+		public String getName() {
+			return name;
+		}
+
+		public String getLanguage() {
+			return lang;
+		}
+
+		public String getGender() {
+			return gender;
+		}
+
 	}
 
 	private Map<String, TreeSet<EngineInfo>> langId2EngineMap = new HashMap<String, TreeSet<EngineInfo>>();
+	private Set<ITTSEngineInfo> ttsEngineInfoSet = new TreeSet<ITTSEngineInfo>(
+			new Comparator<ITTSEngineInfo>() {
+				public int compare(ITTSEngineInfo o1, ITTSEngineInfo o2) {
+					// TODO null, lang/gender check
+					return o1.getName().compareTo(o2.getName());
+				}
+			});
 
 	public SapiVoice() {
 		int pv = COMUtil.createDispatch(ISpVoice.IID);
@@ -105,8 +129,8 @@
 							String voiceName = token.getDescription(0);
 							String langId = token.getAttribute("language"); //$NON-NLS-1$
 							int index = langId.indexOf(";");
-							//use primary lang ID
-							if (index > 0){
+							// use primary lang ID
+							if (index > 0) {
 								langId = langId.substring(0, index);
 							}
 							String gender = token.getAttribute("gender"); //$NON-NLS-1$
@@ -126,8 +150,11 @@
 											});
 									langId2EngineMap.put(langId, set);
 								}
-								set.add(new EngineInfo(voiceName, langId,
-										gender));
+								String lang = LANGID_REVERSE_MAP.get(langId);
+								EngineInfo engineInfo = new EngineInfo(
+										voiceName, lang, langId, gender);
+								set.add(engineInfo);
+								ttsEngineInfoSet.add(engineInfo);
 							}
 						}
 					}
@@ -497,7 +524,6 @@
 				}
 			}
 		}
-
 	}
 
 	/*
@@ -540,7 +566,7 @@
 			// open 100 close 101
 			String tmpS = file.toURI().toString();
 			if (tmpS.startsWith("file:/")) {
-				tmpS = tmpS.substring(6).replaceAll("%20"," ");
+				tmpS = tmpS.substring(6).replaceAll("%20", " ");
 			}
 
 			autoSpFileStream.invoke(100, new Variant[] { new Variant(tmpS),
@@ -572,4 +598,8 @@
 		return speakToFileResult;
 	}
 
+	public Set<ITTSEngineInfo> getTTSEngineInfoSet() {
+		return ttsEngineInfoSet;
+	}
+
 }
diff --git a/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ISAPIEngine.java b/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ISAPIEngine.java
index 3dc51fc..f4ed427 100644
--- a/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ISAPIEngine.java
+++ b/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ISAPIEngine.java
@@ -12,11 +12,12 @@
 
 import java.util.HashMap;
 import java.util.Map;
-
+import java.util.Set;
 
 /**
- * ISAPIEngine interface defines text synthesis interface to be
- * implemented by SAPI5 and MSP text-to-speech engine
+ * ISAPIEngine interface defines text synthesis interface to be implemented by
+ * SAPI5 and MSP text-to-speech engine
+ * 
  * @see ITTSEngine
  */
 public interface ISAPIEngine extends ITTSEngine {
@@ -25,7 +26,6 @@
 			SVSFPurgeBeforeSpeak = 2, SVSFIsFilename = 4, SVSFIsXML = 8,
 			SVSFIsNotXML = 16, SVSFPersistXML = 32;
 
-	
 	/**
 	 * Map to get LangId from "Language"-"Country" code (e.g., en-US).
 	 */
@@ -78,60 +78,60 @@
 			put("zh-HK", "C04");
 		}
 	};
-	
+
 	/**
 	 * Map to get "Language"-"Country" code (e.g., en-US) from LangId.
 	 */
 	public static final Map<String, String> LANGID_REVERSE_MAP = new HashMap<String, String>() {
 		private static final long serialVersionUID = -4065510530588377900L;
 		{
-			put("401","ar-SA");
-			put("402","bg-BG");
-			put("403","ca-ES");
-			put("404","zh-TW");
-			put("405","cs-CZ");
-			put("406","da-DK");
-			put("407","de-DE");
-			put("408","el-GR");
-			put("409","en-US");
-			put("40B","fi-FI");
-			put("40C","fr-FR");
-			put("40D","he-IL");
-			put("40E","hu-HU");
-			put("410","it-IT");
-			put("411","ja-JP");
-			put("412","ko-KR");
-			put("413","nl-NL");
-			put("414","nb-NO");
-			put("415","pl-PL");
-			put("416","pt-BR");
-			put("418","ro-RO");
-			put("419","ru-RU");
-			put("41A","hr-HR");
-			put("41B","sk-SK");
-			put("41D","sv-SE");
-			put("41E","th-TH");
-			put("41F","tr-TR");
-			put("422","uk-UA");
-			put("424","sl-SI");
-			put("425","et-EE");
-			put("426","lv-LV");
-			put("427","lt-LT");
-			put("42A","vi-VN");
-			put("42D","eu-ES");
-			put("804","zh-CN");
-			put("816","pt-PT");
-			put("81A","sr-CS");
-			put("C0A","es-ES");
-			put("C09","en-AU");
-			put("1009","en-CA");
-			put("809","en-GB");
-			put("4009","en-IN");
-			put("C0C","fr-CA");
-			put("C04","zh-HK");
+			put("401", "ar-SA");
+			put("402", "bg-BG");
+			put("403", "ca-ES");
+			put("404", "zh-TW");
+			put("405", "cs-CZ");
+			put("406", "da-DK");
+			put("407", "de-DE");
+			put("408", "el-GR");
+			put("409", "en-US");
+			put("40B", "fi-FI");
+			put("40C", "fr-FR");
+			put("40D", "he-IL");
+			put("40E", "hu-HU");
+			put("410", "it-IT");
+			put("411", "ja-JP");
+			put("412", "ko-KR");
+			put("413", "nl-NL");
+			put("414", "nb-NO");
+			put("415", "pl-PL");
+			put("416", "pt-BR");
+			put("418", "ro-RO");
+			put("419", "ru-RU");
+			put("41A", "hr-HR");
+			put("41B", "sk-SK");
+			put("41D", "sv-SE");
+			put("41E", "th-TH");
+			put("41F", "tr-TR");
+			put("422", "uk-UA");
+			put("424", "sl-SI");
+			put("425", "et-EE");
+			put("426", "lv-LV");
+			put("427", "lt-LT");
+			put("42A", "vi-VN");
+			put("42D", "eu-ES");
+			put("804", "zh-CN");
+			put("816", "pt-PT");
+			put("81A", "sr-CS");
+			put("C0A", "es-ES");
+			put("C09", "en-AU");
+			put("1009", "en-CA");
+			put("809", "en-GB");
+			put("4009", "en-IN");
+			put("C0C", "fr-CA");
+			put("C04", "zh-HK");
 		}
 	};
-	
+
 	/**
 	 * @param rate
 	 *            The rate property to be set.
@@ -143,8 +143,20 @@
 	 * @return The rate property of the voice engine.
 	 */
 	public int getRate();
-	
-	public void speak(String text, int sapiFlags);
-	
 
+	/**
+	 * Speak text by using specified SAPI flag
+	 * 
+	 * @param text
+	 *            text string to be spoken
+	 * @param sapiFlags
+	 *            SAPI flags
+	 */
+	public void speak(String text, int sapiFlags);
+
+	
+	/**
+	 * @return set of TTS engine information that supported in the environment.
+	 */
+	public Set<ITTSEngineInfo> getTTSEngineInfoSet();
 }
diff --git a/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ITTSEngineInfo.java b/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ITTSEngineInfo.java
new file mode 100644
index 0000000..6816c11
--- /dev/null
+++ b/plugins/org.eclipse.actf.ai.voice/src/org/eclipse/actf/ai/tts/ITTSEngineInfo.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright (c) 2012 IBM Corporation and Others
+ * 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
+ *
+ * Contributors:
+ *    Kentarou FUKUDA - initial API and implementation
+ *******************************************************************************/
+
+package org.eclipse.actf.ai.tts;
+
+/**
+ * ITTSEngineInfo enables to store information of text synthesis engines (name, language and gender).
+ * 
+ * @see ITTSEngine
+ */
+public interface ITTSEngineInfo {
+	
+	/**
+	 * @return name of TTS engine
+	 */
+	String getName();
+	
+	/**
+	 * @return language of TTS engine
+	 */
+	String getLanguage();
+	
+	/**
+	 * @return gender of TTS engine
+	 */
+	String getGender();
+}