1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.inputmethod.keyboard.internal; 18 19 import static com.android.inputmethod.keyboard.internal.KeyboardCodesSet.PREFIX_CODE; 20 import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.ICON_UNDEFINED; 21 import static com.android.inputmethod.keyboard.internal.KeyboardIconsSet.PREFIX_ICON; 22 import static com.android.inputmethod.latin.common.Constants.CODE_OUTPUT_TEXT; 23 import static com.android.inputmethod.latin.common.Constants.CODE_UNSPECIFIED; 24 25 import android.test.AndroidTestCase; 26 27 import java.util.Locale; 28 29 abstract class KeySpecParserTestsBase extends AndroidTestCase { 30 private final static Locale TEST_LOCALE = Locale.ENGLISH; 31 protected final KeyboardTextsSet mTextsSet = new KeyboardTextsSet(); 32 33 private static final String CODE_SETTINGS_NAME = "key_settings"; 34 private static final String CODE_SETTINGS = PREFIX_CODE + CODE_SETTINGS_NAME; 35 private static final String ICON_SETTINGS_NAME = "settings_key"; 36 private static final String ICON_SETTINGS = PREFIX_ICON + ICON_SETTINGS_NAME; 37 private static final String CODE_SETTINGS_UPPERCASE = CODE_SETTINGS.toUpperCase(Locale.ROOT); 38 private static final String ICON_SETTINGS_UPPERCASE = ICON_SETTINGS.toUpperCase(Locale.ROOT); 39 private static final String CODE_NON_EXISTING = PREFIX_CODE + "non_existing"; 40 private static final String ICON_NON_EXISTING = PREFIX_ICON + "non_existing"; 41 42 private int mCodeSettings; 43 private int mCodeActionNext; 44 private int mSettingsIconId; 45 46 @Override setUp()47 protected void setUp() throws Exception { 48 super.setUp(); 49 50 mTextsSet.setLocale(TEST_LOCALE, getContext()); 51 mCodeSettings = KeyboardCodesSet.getCode(CODE_SETTINGS_NAME); 52 mCodeActionNext = KeyboardCodesSet.getCode("key_action_next"); 53 mSettingsIconId = KeyboardIconsSet.getIconId(ICON_SETTINGS_NAME); 54 } 55 assertParser(final String message, final String keySpec, final String expectedLabel, final String expectedOutputText, final int expectedIcon, final int expectedCode)56 abstract protected void assertParser(final String message, final String keySpec, 57 final String expectedLabel, final String expectedOutputText, final int expectedIcon, 58 final int expectedCode); 59 assertParserError(final String message, final String keySpec, final String expectedLabel, final String expectedOutputText, final int expectedIconId, final int expectedCode)60 protected void assertParserError(final String message, final String keySpec, 61 final String expectedLabel, final String expectedOutputText, final int expectedIconId, 62 final int expectedCode) { 63 try { 64 assertParser(message, keySpec, expectedLabel, expectedOutputText, expectedIconId, 65 expectedCode); 66 fail(message); 67 } catch (Exception pcpe) { 68 // success. 69 } 70 } 71 72 // \U001d11e: MUSICAL SYMBOL G CLEF 73 private static final String SURROGATE_PAIR1 = "\ud834\udd1e"; 74 private static final int SURROGATE_CODE1 = SURROGATE_PAIR1.codePointAt(0); 75 // \U001d122: MUSICAL SYMBOL F CLEF 76 private static final String SURROGATE_PAIR2 = "\ud834\udd22"; 77 private static final int SURROGATE_CODE2 = SURROGATE_PAIR2.codePointAt(0); 78 // \U002f8a6: CJK COMPATIBILITY IDEOGRAPH-2F8A6; variant character of \u6148. 79 private static final String SURROGATE_PAIR3 = "\ud87e\udca6"; 80 private static final String SURROGATE_PAIRS4 = SURROGATE_PAIR1 + SURROGATE_PAIR2; 81 private static final String SURROGATE_PAIRS5 = SURROGATE_PAIRS4 + SURROGATE_PAIR3; 82 testSingleLetter()83 public void testSingleLetter() { 84 assertParser("Single letter", "a", 85 "a", null, ICON_UNDEFINED, 'a'); 86 assertParser("Single surrogate", SURROGATE_PAIR1, 87 SURROGATE_PAIR1, null, ICON_UNDEFINED, SURROGATE_CODE1); 88 assertParser("Sole vertical bar", "|", 89 "|", null, ICON_UNDEFINED, '|'); 90 assertParser("Single escaped vertical bar", "\\|", 91 "|", null, ICON_UNDEFINED, '|'); 92 assertParser("Single escaped escape", "\\\\", 93 "\\", null, ICON_UNDEFINED, '\\'); 94 assertParser("Single comma", ",", 95 ",", null, ICON_UNDEFINED, ','); 96 assertParser("Single escaped comma", "\\,", 97 ",", null, ICON_UNDEFINED, ','); 98 assertParser("Single escaped letter", "\\a", 99 "a", null, ICON_UNDEFINED, 'a'); 100 assertParser("Single escaped surrogate", "\\" + SURROGATE_PAIR2, 101 SURROGATE_PAIR2, null, ICON_UNDEFINED, SURROGATE_CODE2); 102 assertParser("Single bang", "!", 103 "!", null, ICON_UNDEFINED, '!'); 104 assertParser("Single escaped bang", "\\!", 105 "!", null, ICON_UNDEFINED, '!'); 106 assertParser("Single output text letter", "a|a", 107 "a", null, ICON_UNDEFINED, 'a'); 108 assertParser("Single surrogate pair outputText", "G Clef|" + SURROGATE_PAIR1, 109 "G Clef", null, ICON_UNDEFINED, SURROGATE_CODE1); 110 assertParser("Single letter with outputText", "a|abc", 111 "a", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 112 assertParser("Single letter with surrogate outputText", "a|" + SURROGATE_PAIRS4, 113 "a", SURROGATE_PAIRS4, ICON_UNDEFINED, CODE_OUTPUT_TEXT); 114 assertParser("Single surrogate with outputText", SURROGATE_PAIR3 + "|abc", 115 SURROGATE_PAIR3, "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 116 assertParser("Single letter with escaped outputText", "a|a\\|c", 117 "a", "a|c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 118 assertParser("Single letter with escaped surrogate outputText", 119 "a|" + SURROGATE_PAIR1 + "\\|" + SURROGATE_PAIR2, 120 "a", SURROGATE_PAIR1 + "|" + SURROGATE_PAIR2, ICON_UNDEFINED, CODE_OUTPUT_TEXT); 121 assertParser("Single letter with comma outputText", "a|a,b", 122 "a", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 123 assertParser("Single letter with escaped comma outputText", "a|a\\,b", 124 "a", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 125 assertParser("Single letter with outputText starts with bang", "a|!bc", 126 "a", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 127 assertParser("Single letter with surrogate outputText starts with bang", 128 "a|!" + SURROGATE_PAIRS5, 129 "a", "!" + SURROGATE_PAIRS5, ICON_UNDEFINED, CODE_OUTPUT_TEXT); 130 assertParser("Single letter with outputText contains bang", "a|a!c", 131 "a", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 132 assertParser("Single letter with escaped bang outputText", "a|\\!bc", 133 "a", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 134 assertParser("Single escaped escape with single outputText", "\\\\|\\\\", 135 "\\", null, ICON_UNDEFINED, '\\'); 136 assertParser("Single escaped bar with single outputText", "\\||\\|", 137 "|", null, ICON_UNDEFINED, '|'); 138 assertParser("Single letter with code", "a|" + CODE_SETTINGS, 139 "a", null, ICON_UNDEFINED, mCodeSettings); 140 } 141 testLabel()142 public void testLabel() { 143 assertParser("Simple label", "abc", 144 "abc", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 145 assertParser("Simple surrogate label", SURROGATE_PAIRS4, 146 SURROGATE_PAIRS4, SURROGATE_PAIRS4, ICON_UNDEFINED, CODE_OUTPUT_TEXT); 147 assertParser("Label with escaped bar", "a\\|c", 148 "a|c", "a|c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 149 assertParser("Surrogate label with escaped bar", SURROGATE_PAIR1 + "\\|" + SURROGATE_PAIR2, 150 SURROGATE_PAIR1 + "|" + SURROGATE_PAIR2, SURROGATE_PAIR1 + "|" + SURROGATE_PAIR2, 151 ICON_UNDEFINED, CODE_OUTPUT_TEXT); 152 assertParser("Label with escaped escape", "a\\\\c", 153 "a\\c", "a\\c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 154 assertParser("Label with comma", "a,c", 155 "a,c", "a,c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 156 assertParser("Label with escaped comma", "a\\,c", 157 "a,c", "a,c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 158 assertParser("Label starts with bang", "!bc", 159 "!bc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 160 assertParser("Surrogate label starts with bang", "!" + SURROGATE_PAIRS4, 161 "!" + SURROGATE_PAIRS4, "!" + SURROGATE_PAIRS4, ICON_UNDEFINED, CODE_OUTPUT_TEXT); 162 assertParser("Label contains bang", "a!c", 163 "a!c", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 164 assertParser("Label with escaped bang", "\\!bc", 165 "!bc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 166 assertParser("Label with escaped letter", "\\abc", 167 "abc", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 168 assertParser("Label with outputText", "abc|def", 169 "abc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 170 assertParser("Label with comma and outputText", "a,c|def", 171 "a,c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 172 assertParser("Escaped comma label with outputText", "a\\,c|def", 173 "a,c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 174 assertParser("Escaped label with outputText", "a\\|c|def", 175 "a|c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 176 assertParser("Label with escaped bar outputText", "abc|d\\|f", 177 "abc", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 178 assertParser("Escaped escape label with outputText", "a\\\\|def", 179 "a\\", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 180 assertParser("Label starts with bang and outputText", "!bc|def", 181 "!bc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 182 assertParser("Label contains bang label and outputText", "a!c|def", 183 "a!c", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 184 assertParser("Escaped bang label with outputText", "\\!bc|def", 185 "!bc", "def", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 186 assertParser("Label with comma outputText", "abc|a,b", 187 "abc", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 188 assertParser("Label with escaped comma outputText", "abc|a\\,b", 189 "abc", "a,b", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 190 assertParser("Label with outputText starts with bang", "abc|!bc", 191 "abc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 192 assertParser("Label with outputText contains bang", "abc|a!c", 193 "abc", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 194 assertParser("Label with escaped bang outputText", "abc|\\!bc", 195 "abc", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 196 assertParser("Label with escaped bar outputText", "abc|d\\|f", 197 "abc", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 198 assertParser("Escaped bar label with escaped bar outputText", "a\\|c|d\\|f", 199 "a|c", "d|f", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 200 assertParser("Label with code", "abc|" + CODE_SETTINGS, 201 "abc", null, ICON_UNDEFINED, mCodeSettings); 202 assertParser("Escaped label with code", "a\\|c|" + CODE_SETTINGS, 203 "a|c", null, ICON_UNDEFINED, mCodeSettings); 204 } 205 testCodes()206 public void testCodes() { 207 assertParser("Hexadecimal code", "a|0x1000", 208 "a", null, ICON_UNDEFINED, 0x1000); 209 assertParserError("Illegal hexadecimal code", "a|0x100X", 210 "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); 211 assertParser("Escaped hexadecimal code 1", "a|\\0x1000", 212 "a", "0x1000", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 213 assertParser("Escaped hexadecimal code 2", "a|0\\x1000", 214 "a", "0x1000", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 215 assertParser("Escaped hexadecimal code 2", "a|0\\x1000", 216 "a", "0x1000", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 217 assertParserError("Illegally escaped hexadecimal code", "a|0x1\\000", 218 "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); 219 // This is a workaround to have a key that has a supplementary code point. We can't put a 220 // string in resource as a XML entity of a supplementary code point or a surrogate pair. 221 // TODO: Should pass this test. 222 // assertParser("Hexadecimal supplementary code", String.format("a|0x%06x", SURROGATE_CODE2), 223 // SURROGATE_PAIR2, null, ICON_UNDEFINED, SURROGATE_CODE2); 224 assertParser("Zero is treated as output text", "a|0", 225 "a", null, ICON_UNDEFINED, '0'); 226 assertParser("Digit is treated as output text", "a|3", 227 "a", null, ICON_UNDEFINED, '3'); 228 assertParser("Decimal number is treated as an output text", "a|2014", 229 "a", "2014", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 230 } 231 testIcons()232 public void testIcons() { 233 assertParser("Icon with single letter", ICON_SETTINGS + "|a", 234 null, null, mSettingsIconId, 'a'); 235 assertParser("Icon with outputText", ICON_SETTINGS + "|abc", 236 null, "abc", mSettingsIconId, CODE_OUTPUT_TEXT); 237 assertParser("Icon with outputText starts with bang", ICON_SETTINGS + "|!bc", 238 null, "!bc", mSettingsIconId, CODE_OUTPUT_TEXT); 239 assertParser("Icon with outputText contains bang", ICON_SETTINGS + "|a!c", 240 null, "a!c", mSettingsIconId, CODE_OUTPUT_TEXT); 241 assertParser("Icon with escaped bang outputText", ICON_SETTINGS + "|\\!bc", 242 null, "!bc", mSettingsIconId, CODE_OUTPUT_TEXT); 243 assertParser("Label starts with bang and code", "!bc|" + CODE_SETTINGS, 244 "!bc", null, ICON_UNDEFINED, mCodeSettings); 245 assertParser("Label contains bang and code", "a!c|" + CODE_SETTINGS, 246 "a!c", null, ICON_UNDEFINED, mCodeSettings); 247 assertParser("Escaped bang label with code", "\\!bc|" + CODE_SETTINGS, 248 "!bc", null, ICON_UNDEFINED, mCodeSettings); 249 assertParser("Icon with code", ICON_SETTINGS + "|" + CODE_SETTINGS, 250 null, null, mSettingsIconId, mCodeSettings); 251 } 252 testResourceReference()253 public void testResourceReference() { 254 assertParser("Settings as more key", "!text/keyspec_settings", 255 null, null, mSettingsIconId, mCodeSettings); 256 257 assertParser("Action next as more key", "!text/label_next_key|!code/key_action_next", 258 "Next", null, ICON_UNDEFINED, mCodeActionNext); 259 260 assertParser("Popular domain", 261 "!text/keyspec_popular_domain|!text/keyspec_popular_domain ", 262 ".com", ".com ", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 263 } 264 testFormatError()265 public void testFormatError() { 266 assertParserError("Empty label with outputText", "|a", 267 null, "a", ICON_UNDEFINED, CODE_UNSPECIFIED); 268 assertParserError("Empty label with code", "|" + CODE_SETTINGS, 269 null, null, ICON_UNDEFINED, mCodeSettings); 270 assertParserError("Empty outputText with label", "a|", 271 "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); 272 assertParserError("Empty outputText with icon", ICON_SETTINGS + "|", 273 null, null, mSettingsIconId, CODE_UNSPECIFIED); 274 assertParserError("Icon without code", ICON_SETTINGS, 275 null, null, mSettingsIconId, CODE_UNSPECIFIED); 276 assertParserError("Non existing icon", ICON_NON_EXISTING + "|abc", 277 null, "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 278 assertParserError("Non existing code", "abc|" + CODE_NON_EXISTING, 279 "abc", null, ICON_UNDEFINED, CODE_UNSPECIFIED); 280 assertParserError("Third bar at end", "a|b|", 281 "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); 282 assertParserError("Multiple bar", "a|b|c", 283 "a", null, ICON_UNDEFINED, CODE_UNSPECIFIED); 284 assertParserError("Multiple bar with label and code", "a|" + CODE_SETTINGS + "|c", 285 "a", null, ICON_UNDEFINED, mCodeSettings); 286 assertParserError("Multiple bar with icon and outputText", ICON_SETTINGS + "|b|c", 287 null, null, mSettingsIconId, CODE_UNSPECIFIED); 288 assertParserError("Multiple bar with icon and code", 289 ICON_SETTINGS + "|" + CODE_SETTINGS + "|c", 290 null, null, mSettingsIconId, mCodeSettings); 291 } 292 testUselessUpperCaseSpecifier()293 public void testUselessUpperCaseSpecifier() { 294 assertParser("Single letter with CODE", "a|" + CODE_SETTINGS_UPPERCASE, 295 "a", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 296 assertParser("Label with CODE", "abc|" + CODE_SETTINGS_UPPERCASE, 297 "abc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 298 assertParser("Escaped label with CODE", "a\\|c|" + CODE_SETTINGS_UPPERCASE, 299 "a|c", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 300 assertParser("ICON with outputText", ICON_SETTINGS_UPPERCASE + "|abc", 301 "!ICON/SETTINGS_KEY", "abc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 302 assertParser("ICON with outputText starts with bang", ICON_SETTINGS_UPPERCASE + "|!bc", 303 "!ICON/SETTINGS_KEY", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 304 assertParser("ICON with outputText contains bang", ICON_SETTINGS_UPPERCASE + "|a!c", 305 "!ICON/SETTINGS_KEY", "a!c", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 306 assertParser("ICON with escaped bang outputText", ICON_SETTINGS_UPPERCASE + "|\\!bc", 307 "!ICON/SETTINGS_KEY", "!bc", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 308 assertParser("Label starts with bang and CODE", "!bc|" + CODE_SETTINGS_UPPERCASE, 309 "!bc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 310 assertParser("Label contains bang and CODE", "a!c|" + CODE_SETTINGS_UPPERCASE, 311 "a!c", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 312 assertParser("Escaped bang label with CODE", "\\!bc|" + CODE_SETTINGS_UPPERCASE, 313 "!bc", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 314 assertParser("ICON with CODE", ICON_SETTINGS_UPPERCASE + "|" + CODE_SETTINGS_UPPERCASE, 315 "!ICON/SETTINGS_KEY", "!CODE/KEY_SETTINGS", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 316 assertParser("SETTINGS AS MORE KEY", "!TEXT/SETTINGS_AS_MORE_KEY", 317 "!TEXT/SETTINGS_AS_MORE_KEY", "!TEXT/SETTINGS_AS_MORE_KEY", ICON_UNDEFINED, 318 CODE_OUTPUT_TEXT); 319 assertParser("ACTION NEXT AS MORE KEY", "!TEXT/LABEL_NEXT_KEY|!CODE/KEY_ACTION_NEXT", 320 "!TEXT/LABEL_NEXT_KEY", "!CODE/KEY_ACTION_NEXT", ICON_UNDEFINED, 321 CODE_OUTPUT_TEXT); 322 assertParser("POPULAR DOMAIN", 323 "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN|!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN ", 324 "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN", "!TEXT/KEYLABEL_FOR_POPULAR_DOMAIN ", 325 ICON_UNDEFINED, CODE_OUTPUT_TEXT); 326 assertParserError("Empty label with CODE", "|" + CODE_SETTINGS_UPPERCASE, 327 null, null, ICON_UNDEFINED, mCodeSettings); 328 assertParserError("Empty outputText with ICON", ICON_SETTINGS_UPPERCASE + "|", 329 null, null, mSettingsIconId, CODE_UNSPECIFIED); 330 assertParser("ICON without code", ICON_SETTINGS_UPPERCASE, 331 "!ICON/SETTINGS_KEY", "!ICON/SETTINGS_KEY", ICON_UNDEFINED, CODE_OUTPUT_TEXT); 332 assertParserError("Multiple bar with label and CODE", "a|" + CODE_SETTINGS_UPPERCASE + "|c", 333 "a", null, ICON_UNDEFINED, mCodeSettings); 334 assertParserError("Multiple bar with ICON and outputText", ICON_SETTINGS_UPPERCASE + "|b|c", 335 null, null, mSettingsIconId, CODE_UNSPECIFIED); 336 assertParserError("Multiple bar with ICON and CODE", 337 ICON_SETTINGS_UPPERCASE + "|" + CODE_SETTINGS_UPPERCASE + "|c", 338 null, null, mSettingsIconId, mCodeSettings); 339 } 340 } 341