[309599] setGender and related code
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 733c2e1..4fec6d8 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2008 IBM Corporation and Others
+ * Copyright (c) 2007, 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
@@ -49,6 +49,8 @@
 			.getPreferenceStore();
 	private boolean isDisposed = false;
 
+	private SpObjectToken curVoiceToken = null;
+
 	public MspVoice() {
 		int pv = COMUtil.createDispatch(ISpVoice.IID);
 		dispSpVoice = new ISpVoice(pv);
@@ -66,7 +68,7 @@
 		setAudioOutputName();
 		// switch to actual engine
 		preferenceStore.setValue(ID, orgID);
-		// setVoiceName();
+		setVoiceName(); //for init curVoiceToken
 
 		// to avoid access violation error at application shutdown
 		stop();
@@ -168,8 +170,12 @@
 	 * @return The invocation is succeeded then it returns true.
 	 */
 	public boolean setVoice(Variant varVoice) {
-		return OLE.S_OK == dispSpVoice.put_Voice(varVoice.getDispatch()
-				.getAddress());
+		boolean result = OLE.S_OK == dispSpVoice.put_Voice(varVoice
+				.getDispatch().getAddress());
+		if (result) {
+			curVoiceToken = SpObjectToken.getToken(varVoice);
+		}
+		return result;
 	}
 
 	/**
@@ -359,15 +365,48 @@
 	 * @see org.eclipse.actf.ai.tts.ITTSEngine#setLanguage(java.lang.String)
 	 */
 	public void setLanguage(String language) {
-		String token;
-		if (LANG_JAPANESE.equals(language)) {
-			token = "language=411"; //$NON-NLS-1$
-		} else if (LANG_ENGLISH.equals(language)) {
-			token = "language=409;9"; //$NON-NLS-1$
+		String gender = null;
+		if (curVoiceToken != null) {
+			gender = curVoiceToken.getAttribute("gender");
+		}
+
+		if (gender == null) {
+			gender = "";
 		} else {
+			gender = "gender=" + gender;
+		}
+
+		String langId = LANGID_MAP.get(language);
+		if (langId == null) {
+			// for backward compatibility
+			if (LANG_JAPANESE.equals(language)) {
+				langId = "411"; //$NON-NLS-1$
+			} else if (LANG_ENGLISH.equals(language)) {
+				langId = "409"; //old value "409;9" //$NON-NLS-1$
+			}
+			// TODO other lang
+		}
+		if (langId == null) {
 			return;
 		}
-		setVoiceName(token);
+		String lang = "language=" + langId + ";";
+		
+		// try to keep original gender
+		Variant varVoices = getVoices(lang + gender, null);
+		if (varVoices == null && gender.length() > 0) {
+			varVoices = getVoices(lang, null); // try all
+		}
+		if (varVoices != null) {
+			SpeechObjectTokens tokens = SpeechObjectTokens.getTokens(varVoices);
+			if (null != tokens && 0 < tokens.getCount()) {
+				// TODO priority
+				Variant varVoice = tokens.getItem(0);
+				if (null != varVoice) {
+					setVoice(varVoice);
+				}
+			}
+			varVoices.dispose();
+		}
 	}
 
 	/*
@@ -376,11 +415,33 @@
 	 * @see org.eclipse.actf.ai.tts.ITTSEngine#setGender(java.lang.String)
 	 */
 	public void setGender(String gender) {
-		// TODO
+		String langId = null;
+		if (curVoiceToken != null) {
+			langId = curVoiceToken.getAttribute("language");
+			if ("409;9".equals(langId)) {
+				langId = "409";
+			}
+		}
+		Variant varVoices = null;
+		String lang = "";
+		if (langId != null) {
+			lang = "language=" + langId + ";";
+		}
 		if (GENDER_MALE.equalsIgnoreCase(gender)) {
-			// setVoiceName("name=Microsoft Mike");
+			varVoices = getVoices(lang + "gender=Male", null);
 		} else if (GENDER_FEMALE.equalsIgnoreCase(gender)) {
-			// setVoiceName("name=Microsoft Mary");
+			varVoices = getVoices(lang + "gender=Female", null);
+		}
+		if (null != varVoices) {
+			SpeechObjectTokens tokens = SpeechObjectTokens.getTokens(varVoices);
+			if (null != tokens && 0 < tokens.getCount()) {
+				// TODO priority
+				Variant varVoice = tokens.getItem(0);
+				if (null != varVoice) {
+					setVoice(varVoice);
+				}
+			}
+			varVoices.dispose();
 		}
 	}
 
@@ -452,7 +513,7 @@
 				autoSpFileStream = null;
 			}
 		}
-		setAudioOutputName();	//reset output
+		setAudioOutputName(); // reset output
 		return speakToFileResult;
 	}
 }
diff --git a/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/SpObjectToken.java b/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/SpObjectToken.java
index 5402f12..b8fb580 100644
--- a/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/SpObjectToken.java
+++ b/plugins/org.eclipse.actf.ai.tts.msp/src/org/eclipse/actf/ai/tts/msp/engine/SpObjectToken.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 IBM Corporation and Others
+ * Copyright (c) 2007, 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
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *    Takashi ITOH - initial API and implementation
+ *    Kentarou FUKUDA - initial API and implementation
  *******************************************************************************/
 package org.eclipse.actf.ai.tts.msp.engine;
 
@@ -23,10 +24,12 @@
 
 	private OleAutomation automation;
 	private int idGetDescription;
+	private int idGetAttribute;
 
 	public SpObjectToken(Variant varToken) {
 		automation = varToken.getAutomation();
 		idGetDescription = getIDsOfNames("GetDescription"); //$NON-NLS-1$
+		idGetAttribute = getIDsOfNames("GetAttribute"); //$NON-NLS-1$
 	}
 
 	public static SpObjectToken getToken(Variant varToken) {
@@ -38,8 +41,19 @@
 
 	public String getDescription(int locale) {
 		try {
-			return automation.invoke(idGetDescription,
-					new Variant[] { new Variant(locale) }).getString().trim();
+			return automation
+					.invoke(idGetDescription,
+							new Variant[] { new Variant(locale) }).getString()
+					.trim();
+		} catch (Exception e) {
+		}
+		return null;
+	}
+
+	public String getAttribute(String attr) {
+		try {
+			return automation.invoke(idGetAttribute,
+					new Variant[] { new Variant(attr) }).getString();
 		} catch (Exception e) {
 		}
 		return null;
diff --git a/plugins/org.eclipse.actf.ai.tts.sapi/plugin.properties b/plugins/org.eclipse.actf.ai.tts.sapi/plugin.properties
index add5a93..da23982 100644
--- a/plugins/org.eclipse.actf.ai.tts.sapi/plugin.properties
+++ b/plugins/org.eclipse.actf.ai.tts.sapi/plugin.properties
@@ -1,5 +1,5 @@
 ###############################################################################
-# Copyright (c) 2007 IBM Corporation and others.
+# Copyright (c) 2007, 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
@@ -9,4 +9,4 @@
 #     IBM Corporation - initial API and implementation
 ###############################################################################
 SapiPreferencePage.name=SAPI 5
-voice.exclude=AcTF On-Screen TTS
\ No newline at end of file
+voice.exclude=Sample TTS Voice
\ No newline at end of file
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 c35e4fb..ccab2e4 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007, 2008 IBM Corporation and Others
+ * Copyright (c) 2007, 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
@@ -12,6 +12,10 @@
 package org.eclipse.actf.ai.tts.sapi.engine;
 
 import java.io.File;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TreeSet;
 
 import org.eclipse.actf.ai.tts.ISAPIEngine;
 import org.eclipse.actf.ai.tts.sapi.SAPIPlugin;
@@ -19,6 +23,7 @@
 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;
@@ -49,6 +54,22 @@
 			.getPreferenceStore();
 	private boolean isDisposed = false;
 
+	private SpObjectToken curVoiceToken = null;
+
+	private class EngineInfo {
+		String name;
+		String langId;
+		String gender;
+
+		public EngineInfo(String name, String langId, String gender) {
+			this.name = name;
+			this.langId = langId;
+			this.gender = gender;
+		}
+	}
+
+	private Map<String, TreeSet<EngineInfo>> langId2EngineMap = new HashMap<String, TreeSet<EngineInfo>>();
+
 	public SapiVoice() {
 		int pv = COMUtil.createDispatch(ISpVoice.IID);
 		dispSpVoice = new ISpVoice(pv);
@@ -66,7 +87,53 @@
 		setAudioOutputName();
 		// switch to actual engine
 		preferenceStore.setValue(ID, orgID);
-		// setVoiceName();
+		setVoiceName(); // for init curVoiceToken
+
+		Variant varVoices = getVoices(null, null);
+		if (null != varVoices) {
+			SpeechObjectTokens voiceTokens = SpeechObjectTokens
+					.getTokens(varVoices);
+			if (null != voiceTokens) {
+				String exclude = Platform.getResourceString(SAPIPlugin
+						.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$
+							if ("409;9".equals(langId)) {
+								langId = "409";
+							}
+							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<SapiVoice.EngineInfo>(
+											new Comparator<EngineInfo>() {
+												@Override
+												public int compare(
+														EngineInfo o1,
+														EngineInfo o2) {
+													// TODO priority
+													return -o1.name
+															.compareTo(o2.name);
+												}
+											});
+									langId2EngineMap.put(langId, set);
+								}
+								set.add(new EngineInfo(voiceName, langId,
+										gender));
+							}
+						}
+					}
+				}
+			}
+			varVoices.dispose();
+		}
 
 		// to avoid access violation error at application shutdown
 		stop();
@@ -168,8 +235,12 @@
 	 * @return The invocation is succeeded then it returns true.
 	 */
 	public boolean setVoice(Variant varVoice) {
-		return OLE.S_OK == dispSpVoice.put_Voice(varVoice.getDispatch()
-				.getAddress());
+		boolean result = OLE.S_OK == dispSpVoice.put_Voice(varVoice
+				.getDispatch().getAddress());
+		if (result) {
+			curVoiceToken = SpObjectToken.getToken(varVoice);
+		}
+		return result;
 	}
 
 	/**
@@ -359,15 +430,43 @@
 	 * @see org.eclipse.actf.ai.tts.ITTSEngine#setLanguage(java.lang.String)
 	 */
 	public void setLanguage(String language) {
-		String token;
-		if (LANG_JAPANESE.equals(language)) {
-			token = "language=411"; //$NON-NLS-1$
-		} else if (LANG_ENGLISH.equals(language)) {
-			token = "language=409;9"; //$NON-NLS-1$
-		} else {
+		String gender = null;
+		if (curVoiceToken != null) {
+			gender = curVoiceToken.getAttribute("gender");
+		}
+
+		String langId = LANGID_MAP.get(language);
+		if (langId == null) {
+			// for backward compatibility
+			if (LANG_JAPANESE.equals(language)) {
+				langId = "411"; //$NON-NLS-1$
+			} else if (LANG_ENGLISH.equals(language)) {
+				langId = "409"; //old value "409;9" //$NON-NLS-1$
+			}
+			// TODO other lang
+		}
+		if (langId == null) {
 			return;
 		}
-		setVoiceName(token);
+
+		// Workaround:
+		// In some cases, getVoices("language=***",null) doesn't work well.
+		// So, get all voices first. Then select lang and gender.
+
+		TreeSet<EngineInfo> set = langId2EngineMap.get(langId);
+		if (set != null && set.size() > 0) {
+			if (gender != null) {
+				for (EngineInfo i : set) {
+					if (gender.equalsIgnoreCase(i.gender)) {
+						setVoiceName("name=" + i.name);
+						// System.out.println(i.name);
+						return;
+					}
+				}
+			}
+			setVoiceName("name=" + set.first().name);
+			// System.out.println(set.first().name);
+		}
 	}
 
 	/*
@@ -376,12 +475,28 @@
 	 * @see org.eclipse.actf.ai.tts.ITTSEngine#setGender(java.lang.String)
 	 */
 	public void setGender(String gender) {
-		// TODO
-		if (GENDER_MALE.equalsIgnoreCase(gender)) {
-			setVoiceName("name=Microsoft Mike");
-		} else if (GENDER_FEMALE.equalsIgnoreCase(gender)) {
-			setVoiceName("name=Microsoft Mary");
+		if (gender == null) {
+			return;
 		}
+		String langId = null;
+		if (curVoiceToken != null) {
+			langId = curVoiceToken.getAttribute("language");
+			if ("409;9".equals(langId)) {
+				langId = "409";
+			}
+		}
+
+		TreeSet<EngineInfo> set = langId2EngineMap.get(langId);
+		if (set != null && set.size() > 0) {
+			for (EngineInfo i : set) {
+				if (gender.equalsIgnoreCase(i.gender)) {
+					setVoiceName("name=" + i.name);
+					// System.out.println(i.name);
+					return;
+				}
+			}
+		}
+
 	}
 
 	/*
@@ -452,7 +567,8 @@
 				autoSpFileStream = null;
 			}
 		}
-		setAudioOutputName();	//reset output
+		setAudioOutputName(); // reset output
 		return speakToFileResult;
 	}
+
 }
diff --git a/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SpObjectToken.java b/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SpObjectToken.java
index 4737740..f904a04 100644
--- a/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SpObjectToken.java
+++ b/plugins/org.eclipse.actf.ai.tts.sapi/src/org/eclipse/actf/ai/tts/sapi/engine/SpObjectToken.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2007 IBM Corporation and Others
+ * Copyright (c) 2007, 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
@@ -7,6 +7,7 @@
  *
  * Contributors:
  *    Takashi ITOH - initial API and implementation
+ *    Kentarou FUKUDA - initial API and implementation
  *******************************************************************************/
 package org.eclipse.actf.ai.tts.sapi.engine;
 
@@ -23,10 +24,12 @@
 
 	private OleAutomation automation;
 	private int idGetDescription;
+	private int idGetAttribute;
 
 	public SpObjectToken(Variant varToken) {
 		automation = varToken.getAutomation();
 		idGetDescription = getIDsOfNames("GetDescription"); //$NON-NLS-1$
+		idGetAttribute = getIDsOfNames("GetAttribute"); //$NON-NLS-1$
 	}
 
 	public static SpObjectToken getToken(Variant varToken) {
@@ -38,8 +41,19 @@
 
 	public String getDescription(int locale) {
 		try {
-			return automation.invoke(idGetDescription,
-					new Variant[] { new Variant(locale) }).getString().trim();
+			return automation
+					.invoke(idGetDescription,
+							new Variant[] { new Variant(locale) }).getString()
+					.trim();
+		} catch (Exception e) {
+		}
+		return null;
+	}
+
+	public String getAttribute(String attr) {
+		try {
+			return automation.invoke(idGetAttribute,
+					new Variant[] { new Variant(attr) }).getString();
 		} catch (Exception e) {
 		}
 		return null;
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 7ac30a3..a918872 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2008 IBM Corporation and Others
+ * Copyright (c) 2008, 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
@@ -10,6 +10,9 @@
  *******************************************************************************/
 package org.eclipse.actf.ai.tts;
 
+import java.util.HashMap;
+import java.util.Map;
+
 
 /**
  * ISAPIEngine interface defines text synthesis interface to be
@@ -22,6 +25,113 @@
 			SVSFPurgeBeforeSpeak = 2, SVSFIsFilename = 4, SVSFIsXML = 8,
 			SVSFIsNotXML = 16, SVSFPersistXML = 32;
 
+	
+	/**
+	 * Map to get LangId from "Language"-"Country" code (e.g., en-US).
+	 */
+	public static final Map<String, String> LANGID_MAP = new HashMap<String, String>() {
+		private static final long serialVersionUID = 8393339647554273101L;
+		{
+			put("ar-SA", "401");
+			put("bg-BG", "402");
+			put("ca-ES", "403");
+			put("zh-TW", "404");
+			put("cs-CZ", "405");
+			put("da-DK", "406");
+			put("de-DE", "407");
+			put("el-GR", "408");
+			put("en-US", "409");
+			put("fi-FI", "40B");
+			put("fr-FR", "40C");
+			put("he-IL", "40D");
+			put("hu-HU", "40E");
+			put("it-IT", "410");
+			put("ja-JP", "411");
+			put("ko-KR", "412");
+			put("nl-NL", "413");
+			put("nb-NO", "414");
+			put("pl-PL", "415");
+			put("pt-BR", "416");
+			put("ro-RO", "418");
+			put("ru-RU", "419");
+			put("hr-HR", "41A");
+			put("sk-SK", "41B");
+			put("sv-SE", "41D");
+			put("th-TH", "41E");
+			put("tr-TR", "41F");
+			put("uk-UA", "422");
+			put("sl-SI", "424");
+			put("et-EE", "425");
+			put("lv-LV", "426");
+			put("lt-LT", "427");
+			put("vi-VN", "42A");
+			put("eu-ES", "42D");
+			put("zh-CN", "804");
+			put("pt-PT", "816");
+			put("sr-CS", "81A");
+			put("es-ES", "C0A");
+			put("en-AU", "C09");
+			put("en-CA", "1009");
+			put("en-GB", "809");
+			put("en-IN", "4009");
+			put("fr-CA", "C0C");
+			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");
+		}
+	};
+	
 	/**
 	 * @param rate
 	 *            The rate property to be set.