001package squidpony; 002 003import regexodus.*; 004import java.util.HashMap; 005 006/** 007 * Helps handle formation of messages from a template, using correct pronouns and helping handle various idiosyncrasies 008 * in English-language text. You call the static method 009 * {@link #transform(CharSequence, String, NounTrait, String, NounTrait)} (or one of its overloads) with a template that 010 * has specific placeholder glyphs, along with a user name, optional target name, user NounTrait (an enum in this class) 011 * to specify how the user should be addressed, including their gender, optional target NounTrait, and possibly extra 012 * terms that should be inserted. The placeholder glyphs are usually followed by a specific word that is conjugated in 013 * the template for the first-person case, and will be changed to fit the NounTrait for the user or target. For example, 014 * you could use "@Name hit$ ^ for ~ damage!" as a message. You could transform it with user "Heero Supra", userTrait 015 * NounTrait.SECOND_PERSON_SINGULAR, target "the beast", targetTrait NounTrait.UNSPECIFIED_GENDER, and extra "10" to get 016 * the message "You hit the beast for 10 damage!". You could swap the user and target (along with their traits) to get 017 * the message "The beast hits you for 10 damage!" You can handle more complex verbs in some cases, such as "@I hurr$$$ 018 * to catch up!" can be transformed to "You hurry to catch up!" or "He hurries to catch up!". The rules are fairly 019 * simple; @word conjugates a specific word from a list to the correct kind for the user, while ^word does a similar 020 * thing but conjugates for the target. Between 1 and 3 $ chars can be used at the end of verbs to conjugate them 021 * appropriately for the present tense when the verb is performed by the user (with just $) or alternately the target 022 * (if the $ chars are preceded by a ^), while @s, @ss, @sss, ^s, ^ss, or ^sss can be added at the end of nouns to 023 * pluralize them if appropriate. Using one $ or s will add s or nothing, as in the case of hit becoming hits, using two 024 * $ or s chars will add es or nothing, as in the case of scratch becoming scratches, and using three will add ies or y, 025 * as in the case of carry becoming carries. Some unusual pluralization forms are handled; @usi will turn octop@usi into 026 * octopus or octopi, and radi@usi into radius or radii, while @fves will turn el@fves into elf or elves, or dwar@fves 027 * into dwarf or dwarves. 028 * <br> 029 * The words you can put after a @ or ^ start with a small list and can be added to with 030 * {@link #learnIrregularWord(String, String, String, String, String, String, String)}. The initial list is: name, 031 * name_s, i, me, my, mine, myself, am, have, do, haven_t, don_t, or any of these with the first char capitalized (meant 032 * for words at the start of sentences). The non-word shortened terms "m" and "ve" can be used for "I'm" and "I've", 033 * respectively, as well as "you're" and "you've", plus "he's" for "he is" and "he's" for "he has". Most of the rest 034 * conjugate as you would expect; @me will become him, her, it, them, you, or still more forms depending on userTrait. 035 * You can also use @ or ^ on its own as an equivalent to @name or ^name. 036 * <br> 037 * Examples: 038 * <br> 039 * {@code Messaging.transform("@I @am @my own boss@ss.", "unused", changingTrait)} 040 * <ul> 041 * <li>When changingTrait is {@code NounTrait.FIRST_PERSON_SINGULAR}, this returns "I am my own boss."</li> 042 * <li>When changingTrait is {@code NounTrait.FIRST_PERSON_PLURAL}, this returns "We are our own bosses."</li> 043 * <li>When changingTrait is {@code NounTrait.SECOND_PERSON_SINGULAR}, this returns "You are your own boss."</li> 044 * <li>When changingTrait is {@code NounTrait.SECOND_PERSON_PLURAL}, this returns "You are your own bosses."</li> 045 * <li>When changingTrait is {@code NounTrait.NO_GENDER}, this returns "It is its own boss."</li> 046 * <li>When changingTrait is {@code NounTrait.MALE_GENDER}, this returns "He is his own boss."</li> 047 * <li>When changingTrait is {@code NounTrait.FEMALE_GENDER}, this returns "She is her own boss."</li> 048 * <li>When changingTrait is {@code NounTrait.UNSPECIFIED_GENDER}, this returns "They are their own boss."</li> 049 * <li>When changingTrait is {@code NounTrait.ADDITIONAL_GENDER}, this returns "Xe is xis own boss."</li> 050 * <li>When changingTrait is {@code NounTrait.SPECIAL_CASE_GENDER}, this returns "Qvqe is qvqis own boss."</li> 051 * <li>When changingTrait is {@code NounTrait.GROUP}, this returns "They are their own bosses."</li> 052 * </ul> 053 * {@code Messaging.transform("@Name spit$ in ^name_s face^s!", userName, userTrait, targetName, targetTrait)} 054 * <ul> 055 * <li>When userTrait is {@code NounTrait.SECOND_PERSON_SINGULAR}, targetName is {@code "the goblin"}, and 056 * targetTrait is {@code NounTrait.MALE_GENDER}, this returns "You spit in the goblin's face!"</li> 057 * <li>When userName is {@code "the goblin"}, userTrait is {@code NounTrait.MALE_GENDER}, and targetTrait is 058 * {@code NounTrait.SECOND_PERSON_SINGULAR}, this returns "The goblin spits in your face!"</li> 059 * <li>When userTrait is {@code NounTrait.SECOND_PERSON_SINGULAR}, targetName is {@code "the goblins"}, and 060 * targetTrait is {@code NounTrait.GROUP}, this returns "You spit in the goblins' faces!"</li> 061 * <li>When userName is {@code "the goblins"}, userTrait is {@code NounTrait.GROUP}, and targetTrait is 062 * {@code NounTrait.SECOND_PERSON_SINGULAR}, this returns "The goblins spit in your face!"</li> 063 * </ul> 064 * Created by Tommy Ettinger on 10/31/2016. 065 */ 066public class Messaging { 067 068 /** 069 * Properties of nouns needed to correctly conjugate those nouns and refer to them with pronouns, such as genders. 070 * Includes parts of speech, which only are concerned with whether they refer to a singular noun or a plural noun, 071 * and genders for when a gendered pronoun is needed. This provides substantial support for uncommon cases regarding 072 * gender and pronoun preferences. That said, gender and pronoun preference can be incredibly hard to handle. 073 * The simplest cases are for first- and second-person pronouns; here we have "I/me/my/myself" for 074 * {@link #FIRST_PERSON_SINGULAR}, "you/you/your/yourself" for {@link #SECOND_PERSON_SINGULAR}, 075 * "we/us/our/ourselves" for {@link #FIRST_PERSON_PLURAL}, and "you/you/your/yourselves" for 076 * {@link #SECOND_PERSON_PLURAL}; there are more pronouns this can produce, but they aren't listed here. 077 * Third-person pronouns are considerably more challenging because English sporadically considers gender as part of 078 * conjugation, but doesn't provide a universally-acceptable set of gendered pronouns. 079 * <br> 080 * This at least tries to provide pronoun handling for the common cases, such as "you" not needing a gendered 081 * pronoun at all (it uses {@link #SECOND_PERSON_SINGULAR}), and supports {@link #MALE_GENDER male}, 082 * {@link #FEMALE_GENDER female}, {@link #NO_GENDER genderless} (using "it" and related forms; preferred especially 083 * for things that aren't alive, and in most cases not recommended for people), 084 * {@link #UNSPECIFIED_GENDER "unspecified"} (using "they" in place of "he" or "she"; preferred in some cases when 085 * describing someone with a non-specific gender or an unknown gender) pronouns, and {@link #GROUP group} for when a 086 * group of individuals, regardless of gender or genders, is referred to with a single pronoun. As mentioned, this 087 * has support for some uncommon situations, like {@link #ADDITIONAL_GENDER additional gender} (as in, a gender that 088 * is in addition to male and female but that is not genderless, which has a clear use case when describing 089 * non-human species, and a more delicate use for humans who use non-binary gender pronouns; hopefully "xe" will be 090 * acceptable), and finally a {@link #SPECIAL_CASE_GENDER "special case"} pronoun that is unpronounceable and, if 091 * given special processing, can be used as a replacement target for customized pronouns. For the additional gender, 092 * the non-binary gendered pronouns are modified from the male pronouns by replacing 'h' with 'x' (he becomes xe, 093 * his becomes xis). The "special case" pronouns replace the 'h' in the male pronouns with 'qvq', except for in one 094 * case. Where, if the female pronoun were used, it would be "hers", but the male pronoun in that case would be "his", 095 * changing the male pronoun would lead to a difficult-to-replace case because "his" is also used in the case where 096 * the female pronoun is the usefully distinct "her". Here, the "special case" gender diverges from what it usually 097 * does, and uses "qvqims" in place of "his" or "hers". The "special case" pronouns should be replaced before being 098 * displayed, since they look like gibberish or a glitch and so are probably confusing out of context. 099 */ 100 public enum NounTrait { 101 /** 102 * As in, "I am my own boss." Doesn't reference gender. 103 */ 104 FIRST_PERSON_SINGULAR, 105 /** 106 * As in, "You are your own boss." Doesn't reference gender. 107 */ 108 SECOND_PERSON_SINGULAR, 109 /** 110 * As in, "We are our own bosses." Doesn't reference gender, and applies to groups. 111 */ 112 FIRST_PERSON_PLURAL, 113 /** 114 * As in, "You are your own bosses." Doesn't reference gender, and applies to groups. 115 */ 116 SECOND_PERSON_PLURAL, 117 /** 118 * Inanimate objects or beings without gender, as in "It is its own boss." 119 */ 120 NO_GENDER, 121 /** 122 * Male pronoun preference, as in "He is his own boss." 123 */ 124 MALE_GENDER, 125 /** 126 * Female pronoun preference, as in "She is her own boss." 127 */ 128 FEMALE_GENDER, 129 /** 130 * "Singular they" pronoun preference or to be used when preference is unknown, as in "They are their own boss." 131 */ 132 UNSPECIFIED_GENDER, 133 /** 134 * Third-gender pronoun preference, potentially relevant for cultures with non-binary gender terms. As in, "Xe 135 * is xis own boss." 136 */ 137 ADDITIONAL_GENDER, 138 /** 139 * Unpronounceable words that can be processed specially for more complex cases of pronoun preference. As in, 140 * "Qvqe is qvqis own boss." 141 */ 142 SPECIAL_CASE_GENDER, 143 /** 144 * Any third-person plural, as in "They are their own bosses." Not to be confused with UNSPECIFIED_GENDER, which 145 * is for singular beings, but usually uses "they" in the same way (not always). 146 */ 147 GROUP; 148 149 public String nameText(String term) { 150 switch (this) { 151 case FIRST_PERSON_SINGULAR: 152 return "I"; 153 case FIRST_PERSON_PLURAL: 154 return "we"; 155 case SECOND_PERSON_SINGULAR: 156 case SECOND_PERSON_PLURAL: 157 return "you"; 158 default: 159 return term; 160 } 161 } 162 public String name_sText(String term) { 163 switch (this) { 164 case FIRST_PERSON_SINGULAR: 165 return "my"; 166 case FIRST_PERSON_PLURAL: 167 return "our"; 168 case SECOND_PERSON_SINGULAR: 169 case SECOND_PERSON_PLURAL: 170 return "your"; 171 default: 172 if(term.isEmpty()) return ""; 173 else if(term.endsWith("s")) return term + '\''; 174 else return term + "'s"; 175 } 176 } 177 178 public String iText() { 179 switch (this) { 180 case FIRST_PERSON_SINGULAR: 181 return "I"; 182 case FIRST_PERSON_PLURAL: 183 return "we"; 184 case SECOND_PERSON_SINGULAR: 185 case SECOND_PERSON_PLURAL: 186 return "you"; 187 case NO_GENDER: 188 return "it"; 189 case MALE_GENDER: 190 return "he"; 191 case FEMALE_GENDER: 192 return "she"; 193 //case UNSPECIFIED_GENDER: return "they"; 194 case ADDITIONAL_GENDER: 195 return "xe"; 196 case SPECIAL_CASE_GENDER: 197 return "qvqe"; 198 default: 199 return "they"; 200 } 201 } 202 public String meText() { 203 switch (this) { 204 case FIRST_PERSON_SINGULAR: 205 return "me"; 206 case FIRST_PERSON_PLURAL: 207 return "us"; 208 case SECOND_PERSON_SINGULAR: 209 case SECOND_PERSON_PLURAL: 210 return "you"; 211 case NO_GENDER: 212 return "it"; 213 case MALE_GENDER: 214 return "him"; 215 case FEMALE_GENDER: 216 return "her"; 217 //case UNSPECIFIED_GENDER: return "them"; 218 case ADDITIONAL_GENDER: 219 return "xim"; 220 case SPECIAL_CASE_GENDER: 221 return "qvqim"; 222 default: 223 return "them"; 224 } 225 } 226 227 public String myText() { 228 switch (this) { 229 case FIRST_PERSON_SINGULAR: 230 return "my"; 231 case FIRST_PERSON_PLURAL: 232 return "our"; 233 case SECOND_PERSON_SINGULAR: 234 case SECOND_PERSON_PLURAL: 235 return "your"; 236 case NO_GENDER: 237 return "its"; 238 case MALE_GENDER: 239 return "his"; 240 case FEMALE_GENDER: 241 return "her"; 242 //case UNSPECIFIED_GENDER: return "their"; 243 case ADDITIONAL_GENDER: 244 return "xis"; 245 case SPECIAL_CASE_GENDER: 246 return "qvqis"; 247 default: 248 return "their"; 249 } 250 } 251 public String mineText() { 252 switch (this) { 253 case FIRST_PERSON_SINGULAR: 254 return "mine"; 255 case FIRST_PERSON_PLURAL: 256 return "ours"; 257 case SECOND_PERSON_SINGULAR: 258 case SECOND_PERSON_PLURAL: 259 return "yours"; 260 case NO_GENDER: 261 return "its"; 262 case MALE_GENDER: 263 return "his"; 264 case FEMALE_GENDER: 265 return "hers"; 266 //case UNSPECIFIED_GENDER: return "theirs"; 267 case ADDITIONAL_GENDER: 268 return "xis"; 269 case SPECIAL_CASE_GENDER: 270 return "qvqims"; 271 default: 272 return "theirs"; 273 } 274 } 275 276 public String myselfText() { 277 switch (this) { 278 case FIRST_PERSON_SINGULAR: 279 return "myself"; 280 case FIRST_PERSON_PLURAL: 281 return "ourselves"; 282 case SECOND_PERSON_SINGULAR: 283 return "yourself"; 284 case SECOND_PERSON_PLURAL: 285 return "yourselves"; 286 case NO_GENDER: 287 return "itself"; 288 case MALE_GENDER: 289 return "himself"; 290 case FEMALE_GENDER: 291 return "herself"; 292 case UNSPECIFIED_GENDER: 293 return "themself"; 294 case ADDITIONAL_GENDER: 295 return "ximself"; 296 case SPECIAL_CASE_GENDER: 297 return "qvqimself"; 298 default: 299 return "themselves"; 300 } 301 } 302 303 public String sText() { 304 switch (this) { 305 case FIRST_PERSON_PLURAL: 306 case SECOND_PERSON_PLURAL: 307 case GROUP: 308 return "s"; 309 default: 310 return ""; 311 } 312 } 313 public String ssText() { 314 switch (this) { 315 case FIRST_PERSON_PLURAL: 316 case SECOND_PERSON_PLURAL: 317 case GROUP: 318 return "es"; 319 default: 320 return ""; 321 } 322 } 323 public String sssText() { 324 switch (this) { 325 case FIRST_PERSON_PLURAL: 326 case SECOND_PERSON_PLURAL: 327 case GROUP: 328 return "ies"; 329 default: 330 return "y"; 331 } 332 } 333 public String usiText() { 334 switch (this) { 335 case FIRST_PERSON_PLURAL: 336 case SECOND_PERSON_PLURAL: 337 case GROUP: 338 return "i"; 339 default: 340 return "us"; 341 } 342 } 343 public String fvesText() { 344 switch (this) { 345 case FIRST_PERSON_PLURAL: 346 case SECOND_PERSON_PLURAL: 347 case GROUP: 348 return "ves"; 349 default: 350 return "f"; 351 } 352 } 353 354 public String $Text() { 355 switch (this) { 356 case FIRST_PERSON_SINGULAR: 357 case FIRST_PERSON_PLURAL: 358 case SECOND_PERSON_SINGULAR: 359 case SECOND_PERSON_PLURAL: 360 case UNSPECIFIED_GENDER: 361 case GROUP: 362 return ""; 363 default: 364 return "s"; 365 } 366 } 367 public String $$Text() { 368 switch (this) { 369 case FIRST_PERSON_SINGULAR: 370 case FIRST_PERSON_PLURAL: 371 case SECOND_PERSON_SINGULAR: 372 case SECOND_PERSON_PLURAL: 373 case UNSPECIFIED_GENDER: 374 case GROUP: 375 return ""; 376 default: 377 return "es"; 378 } 379 } 380 public String $$$Text() { 381 switch (this) { 382 case FIRST_PERSON_SINGULAR: 383 case FIRST_PERSON_PLURAL: 384 case SECOND_PERSON_SINGULAR: 385 case SECOND_PERSON_PLURAL: 386 case UNSPECIFIED_GENDER: 387 case GROUP: 388 return "y"; 389 default: 390 return "ies"; 391 } 392 } 393 394 public String directText(String term) { 395 return term; 396 } 397 398 } 399 400 public static class Group 401 { 402 public String[] members; 403 404 public Group() 405 { 406 members = new String[]{"the goblin", "the dragon", "the warlock"}; 407 } 408 public Group(String... members) 409 { 410 this.members = members; 411 } 412 } 413 414 415 /** 416 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 417 * conjugated terms for the given user and their associated NounTrait. 418 * @param message the message to transform; should contain "@" or "$" in it, at least, to be replaced 419 * @param user the name of the user for cases where it can replace text like "@" or "@Name" 420 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 421 * @return a String resulting from the processing of message 422 */ 423 public static String transform(CharSequence message, String user, NounTrait userTrait) 424 { 425 Replacer ur = new Replacer(userPattern, new BeingSubstitution(user, userTrait, true)); 426 return ur.replace(message); 427 } 428 429 /** 430 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 431 * conjugated terms for the given group of users and that group's associated NounTrait. The NounTrait only matters 432 * if it is first-person or second-person (in which case this uses the plural form) or if the Group contains one 433 * member (in which case it uses any gendered pronouns specified by userTrait); it uses {@link NounTrait#GROUP} in 434 * any other case. 435 * @param message the message to transform; should contain "@" or "$" in it, at least, to be replaced 436 * @param user a {@link Group} of users (a String array) for cases where it can replace text, like "@" or "@Name" 437 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 438 * @return a String resulting from the processing of message 439 */ 440 public static String transform(CharSequence message, Group user, NounTrait userTrait) 441 { 442 Replacer ur = new Replacer(userPattern, new BeingSubstitution(userTrait, true, user.members)); 443 return ur.replace(message); 444 } 445 446 /** 447 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 448 * conjugated terms for the given user, their associated NounTrait, the given target, and their NounTrait. 449 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 450 * @param user the name of the user for cases where it can replace text like "@" or "@Name" 451 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 452 * @param target the name of the target for cases where it can replace text like "^" or "^Name" 453 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 454 * @return a String resulting from the processing of message 455 */ 456 public static String transform(CharSequence message, String user, NounTrait userTrait, String target, NounTrait targetTrait) 457 { 458 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(target, targetTrait, false)), 459 ur = new Replacer(userPattern, new BeingSubstitution(user, userTrait, true)); 460 return ur.replace(tr.replace(message)); 461 } 462 463 /** 464 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 465 * conjugated terms for the given Group of users, that group's associated NounTrait, the given target, and their 466 * NounTrait. The NounTrait for user only matters if it is first-person or second-person (in which case this uses 467 * the plural form) or if the Group contains one member (in which case it uses any gendered pronouns specified by 468 * userTrait); it uses {@link NounTrait#GROUP} in any other case. 469 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 470 * @param user the {@link Group} of users for cases where they can replace text like "@" or "@Name" 471 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 472 * @param target the name of the target for cases where it can replace text like "^" or "^Name" 473 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 474 * @return a String resulting from the processing of message 475 */ 476 public static String transform(CharSequence message, Group user, NounTrait userTrait, String target, NounTrait targetTrait) 477 { 478 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(target, targetTrait, false)), 479 ur = new Replacer(userPattern, new BeingSubstitution(userTrait, true, user.members)); 480 return ur.replace(tr.replace(message)); 481 } 482 483 /** 484 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 485 * conjugated terms for the given Group of users, that group's associated NounTrait, the given group of targets, and 486 * that group's NounTrait. The NounTraits only matter if they are is first-person or second-person (in which case 487 * this uses the plural form) or if a Group contains one member (in which case it uses any gendered pronouns 488 * specified by userTrait or targetTrait); it uses {@link NounTrait#GROUP} in any other case. 489 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 490 * @param user the {@link Group} of users for cases where they can replace text like "@" or "@Name" 491 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 492 * @param target the {@link Group} of targets for cases where they can replace text like "@" or "@Name" 493 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 494 * @return a String resulting from the processing of message 495 */ 496 public static String transform(CharSequence message, Group user, NounTrait userTrait, Group target, NounTrait targetTrait) 497 { 498 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(targetTrait, false, target.members)), 499 ur = new Replacer(userPattern, new BeingSubstitution(userTrait, true, user.members)); 500 return ur.replace(tr.replace(message)); 501 } 502 503 504 /** 505 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 506 * conjugated terms for the given user, that user's associated NounTrait, the given Group of targets, and that 507 * group's NounTrait. The NounTrait for target only matters if it is first-person or second-person (in which case 508 * this uses the plural form) or if the Group contains one member (in which case it uses any gendered pronouns 509 * specified by targetTrait); it uses {@link NounTrait#GROUP} in any other case. 510 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 511 * @param user the name of the user for cases where it can replace text like "@" or "@Name" 512 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 513 * @param target the {@link Group} of targets for cases where they can replace text like "@" or "@Name" 514 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 515 * @return a String resulting from the processing of message 516 */ 517 public static String transform(CharSequence message, String user, NounTrait userTrait, Group target, NounTrait targetTrait) 518 { 519 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(targetTrait, false, target.members)), 520 ur = new Replacer(userPattern, new BeingSubstitution(user, userTrait, true)); 521 return ur.replace(tr.replace(message)); 522 } 523 524 /** 525 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 526 * conjugated terms for the given user, their associated NounTrait, the given target, and their NounTrait. Also 527 * replaces the nth occurrence of "~" with the matching nth item in extra, so the first "~" is replaced with the 528 * first item in extra, the second "~" with the second item, and so on until one is exhausted. 529 * @param message the message to transform; should contain "@", "^", "$", or "~" in it, at least, to be replaced 530 * @param user the name of the user for cases where it can replace text like "@" or "@Name" 531 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 532 * @param target the name of the target for cases where it can replace text like "^" or "^Name" 533 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 534 * @param extra an array or vararg of String where the nth item in extra will replace the nth occurrence of "~" 535 * @return a String resulting from the processing of message 536 */ 537 public static String transform(CharSequence message, String user, NounTrait userTrait, String target, NounTrait targetTrait, String... extra) 538 { 539 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(target, targetTrait, false)), 540 ur = new Replacer(userPattern, new BeingSubstitution(user, userTrait, true)); 541 String text = ur.replace(tr.replace(message)); 542 if(extra != null && extra.length > 0) 543 { 544 for (int i = 0; i < extra.length; i++) { 545 text = text.replaceFirst("~", extra[i]); 546 } 547 } 548 return text; 549 } 550 551 /** 552 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 553 * conjugated terms for the given Group of users, that group's associated NounTrait, the given target, and their 554 * NounTrait. Also replaces the nth occurrence of "~" with the matching nth item in extra, so the first "~" is 555 * replaced with the first item in extra, the second "~" with the second item, and so on until one is exhausted. The 556 * NounTrait for user only matters if it is first-person or second-person (in which case this uses the plural form) 557 * or if the Group contains one member (in which case it uses any gendered pronouns specified by userTrait); it uses 558 * {@link NounTrait#GROUP} in any other case. 559 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 560 * @param user the {@link Group} of users for cases where they can replace text like "@" or "@Name" 561 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 562 * @param target the name of the target for cases where it can replace text like "^" or "^Name" 563 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 564 * @param extra an array or vararg of String where the nth item in extra will replace the nth occurrence of "~" 565 * @return a String resulting from the processing of message 566 */ 567 public static String transform(CharSequence message, Group user, NounTrait userTrait, String target, NounTrait targetTrait, String... extra) 568 { 569 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(target, targetTrait, false)), 570 ur = new Replacer(userPattern, new BeingSubstitution(userTrait, true, user.members)); 571 String text = ur.replace(tr.replace(message)); 572 if(extra != null && extra.length > 0) 573 { 574 for (int i = 0; i < extra.length; i++) { 575 text = text.replaceFirst("~", extra[i]); 576 } 577 } 578 return text; 579 } 580 581 /** 582 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 583 * conjugated terms for the given Group of users, that group's associated NounTrait, the given group of targets, and 584 * that group's NounTrait. Also replaces the nth occurrence of "~" with the matching nth item in extra, so the first 585 * "~" is replaced with the first item in extra, the second "~" with the second item, and so on until one is 586 * exhausted. The NounTraits only matter if they are is first-person or second-person (in which case 587 * this uses the plural form) or if a Group contains one member (in which case it uses any gendered pronouns 588 * specified by userTrait or targetTrait); it uses {@link NounTrait#GROUP} in any other case. 589 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 590 * @param user the {@link Group} of users for cases where they can replace text like "@" or "@Name" 591 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 592 * @param target the {@link Group} of targets for cases where they can replace text like "@" or "@Name" 593 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 594 * @param extra an array or vararg of String where the nth item in extra will replace the nth occurrence of "~" 595 * @return a String resulting from the processing of message 596 */ 597 public static String transform(CharSequence message, Group user, NounTrait userTrait, Group target, NounTrait targetTrait, String... extra) 598 { 599 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(targetTrait, false, target.members)), 600 ur = new Replacer(userPattern, new BeingSubstitution(userTrait, true, user.members)); 601 String text = ur.replace(tr.replace(message)); 602 if(extra != null && extra.length > 0) 603 { 604 for (int i = 0; i < extra.length; i++) { 605 text = text.replaceFirst("~", extra[i]); 606 } 607 } 608 return text; 609 } 610 611 612 /** 613 * Takes message and replaces any of the special terms this recognizes, like @, ^, and $, with the appropriately- 614 * conjugated terms for the given user, that user's associated NounTrait, the given Group of targets, and that 615 * group's NounTrait. Also replaces the nth occurrence of "~" with the matching nth item in extra, so the first "~" 616 * is replaced with the first item in extra, the second "~" with the second item, and so on until one is exhausted. 617 * The NounTrait for target only matters if it is first-person or second-person (in which case this uses the plural 618 * form) or if the Group contains one member (in which case it uses any gendered pronouns specified by targetTrait); 619 * it uses {@link NounTrait#GROUP} in any other case. 620 * @param message the message to transform; should contain "@", "^", or "$" in it, at least, to be replaced 621 * @param user the name of the user for cases where it can replace text like "@" or "@Name" 622 * @param userTrait the {@link NounTrait} enum that determines how user should be referred to 623 * @param target the {@link Group} of targets for cases where they can replace text like "@" or "@Name" 624 * @param targetTrait the {@link NounTrait} enum that determines how the target should be referred to 625 * @param extra an array or vararg of String where the nth item in extra will replace the nth occurrence of "~" 626 * @return a String resulting from the processing of message 627 */ 628 public static String transform(CharSequence message, String user, NounTrait userTrait, Group target, NounTrait targetTrait, String... extra) 629 { 630 Replacer tr = new Replacer(targetPattern, new BeingSubstitution(targetTrait, false, target.members)), 631 ur = new Replacer(userPattern, new BeingSubstitution(user, userTrait, true)); 632 String text = ur.replace(tr.replace(message)); 633 if(extra != null && extra.length > 0) 634 { 635 for (int i = 0; i < extra.length; i++) { 636 text = text.replaceFirst("~", extra[i]); 637 } 638 } 639 return text; 640 } 641 642 643 protected static final Pattern 644 userPattern = Pattern.compile("({at_sign}\\\\@)|({caret_sign}\\\\\\^)|({dollar_sign}\\\\\\$)|({tilde_sign}\\\\~)|" + 645 "({$$$}\\$\\$\\$)|({$$}\\$\\$)|({$}\\$)|({sss}@sss\\b)|({ss}@ss\\b)|({s}@s\\b)|({usi}@usi\\b)|({fves}@fves\\b)|" + 646 "({name_s}@name_s\\b)|({Name_s}@Name_s\\b)|({name}@name\\b)|({Name}@Name\\b)|({i}@i\\b)|({I}@I\\b)|({me}@me\\b)|({Me}@Me\\b)|" + 647 "({myself}@myself\\b)|({Myself}@Myself\\b)|({my}@my\\b)|({My}@My\\b)|({mine}@mine\\b)|({Mine}@Mine\\b)|({direct}@direct\\b)|" + 648 "({Direct}@Direct\\b)|(?:@({Other}\\p{Lu}\\w*)\\b)|(?:@({other}\\p{Ll}\\w*)\\b)|({=name}@)"), 649 targetPattern = Pattern.compile("({at_sign}\\\\@)|({caret_sign}\\\\\\^)|({dollar_sign}\\\\\\$)|({tilde_sign}\\\\~)|" + 650 "({$$$}\\^\\$\\$\\$)|({$$}\\^\\$\\$)|({$}\\^\\$)|({sss}\\^sss\\b)|({ss}\\^ss\\b)|({s}\\^s\\b)|({usi}\\^usi\\b)|({fves}\\^fves\\b)|" + 651 "({name_s}\\^name_s\\b)|({Name_s}\\^Name_s\\b)|({name}\\^name\\b)|({Name}\\^Name\\b)|({i}\\^i\\b)|({I}\\^I\\b)|({me}\\^me\\b)|({Me}\\^Me\\b)|" + 652 "({myself}\\^myself\\b)|({Myself}\\^Myself\\b)|({my}\\^my\\b)|({My}\\^My\\b)|({mine}\\^mine\\b)|({Mine}\\^Mine\\b)|({direct}^direct\\b)|" + 653 "({Direct}^Direct\\b)|(?:\\^({Other}\\p{Lu}\\w*)\\b)|(?:\\^({other}\\p{Ll}\\w*)\\b)|({=name}\\^)"); 654 655 private static final HashMap<String, String[]> irregular = new HashMap<>(64); 656 657 /** 658 * Adds a given {@code word}, which should start with a lower-case letter and use lower-case letters and underscores 659 * only, to the dictionary this stores. The 6 additional arguments are used for first person singular ("I am"), 660 * first person plural ("we are"), second person singular ("you are"), second person plural ("you are", the same 661 * as the last one usually, but not always), third person singular ("he is"), third person plural ("they are"). 662 * @param word the word to learn; must start with a letter and use only lower-case letters and underscores 663 * @param firstPersonSingular the conjugated form of the word for first-person singular ("I do", "I am") 664 * @param firstPersonPlural the conjugated form of the word for first-person plural ("we do", "we are") 665 * @param secondPersonSingular the conjugated form of the word for second-person singular ("you do", "you are") 666 * @param secondPersonPlural the conjugated form of the word for second-person plural ("you do", "you are") 667 * @param thirdPersonSingular the conjugated form of the word for third-person singular ("he does", "he is") 668 * @param thirdPersonPlural the conjugated form of the word for third-person plural and unspecified-gender singular ("they do", "they are") 669 */ 670 public static void learnIrregularWord(String word, String firstPersonSingular, String firstPersonPlural, 671 String secondPersonSingular, String secondPersonPlural, 672 String thirdPersonSingular, String thirdPersonPlural) 673 { 674 irregular.put(word, new String[]{firstPersonSingular, firstPersonPlural, secondPersonSingular, secondPersonPlural, 675 thirdPersonSingular, thirdPersonSingular, thirdPersonSingular, thirdPersonPlural, thirdPersonSingular, thirdPersonSingular, 676 thirdPersonPlural}); 677 } 678 679 static { 680 learnIrregularWord("m", "'m", "'re", "'re", "'re", "'s", "'re"); 681 learnIrregularWord("am", "am", "are", "are", "are", "is", "are"); 682 learnIrregularWord("ve", "'ve", "'ve", "'ve", "'ve", "'s", "'ve"); 683 learnIrregularWord("have", "have", "have", "have", "have", "has", "have"); 684 learnIrregularWord("haven_t", "haven't", "haven't", "haven't", "haven't", "hasn't", "haven't"); 685 learnIrregularWord("do", "do", "do", "do", "do", "does", "do"); 686 learnIrregularWord("don_t", "don't", "don't", "don't", "don't", "doesn't", "don't"); 687 learnIrregularWord("this", "this", "these", "this", "these", "this", "these"); 688 } 689 690 protected static class BeingSubstitution implements Substitution { 691 692 public String term; 693 public NounTrait trait; 694 public boolean finisher; 695 public BeingSubstitution() 696 { 697 term = "Joe"; 698 trait = NounTrait.MALE_GENDER; 699 finisher = true; 700 } 701 702 public BeingSubstitution(String term, NounTrait trait, boolean finish) 703 { 704 this.term = (term == null) ? "Nullberoth of the North" : term; 705 this.trait = (trait == null) ? NounTrait.UNSPECIFIED_GENDER : trait; 706 finisher = finish; 707 } 708 public BeingSubstitution(NounTrait firstTrait, boolean finish, String... terms) { 709 int len; 710 if (terms == null || (len = terms.length) <= 0) { 711 term = "Nihilatia of Voidetica"; 712 trait = (firstTrait == null) ? NounTrait.UNSPECIFIED_GENDER : firstTrait; 713 finisher = finish; 714 } else if (len == 1) { 715 term = (terms[0] == null) ? "Nullberoth of the North" : terms[0]; 716 trait = NounTrait.UNSPECIFIED_GENDER; 717 finisher = finish; 718 } else if (len == 2) { 719 term = terms[0] + " and " + terms[1]; 720 if (firstTrait == null) 721 trait = NounTrait.GROUP; 722 else { 723 switch (firstTrait) { 724 case FIRST_PERSON_PLURAL: 725 case FIRST_PERSON_SINGULAR: 726 trait = NounTrait.FIRST_PERSON_PLURAL; 727 break; 728 case SECOND_PERSON_PLURAL: 729 case SECOND_PERSON_SINGULAR: 730 trait = NounTrait.SECOND_PERSON_PLURAL; 731 break; 732 default: 733 trait = NounTrait.GROUP; 734 } 735 } 736 finisher = finish; 737 } else { 738 StringBuilder sb = new StringBuilder().append(terms[0]).append(", "); 739 for (int i = 1; i < len - 1; i++) { 740 sb.append(terms[i]).append(", "); 741 } 742 term = sb.append("and ").append(terms[len - 1]).toString(); 743 if (firstTrait == null) 744 trait = NounTrait.GROUP; 745 else { 746 switch (firstTrait) { 747 case FIRST_PERSON_PLURAL: 748 case FIRST_PERSON_SINGULAR: 749 trait = NounTrait.FIRST_PERSON_PLURAL; 750 break; 751 case SECOND_PERSON_PLURAL: 752 case SECOND_PERSON_SINGULAR: 753 trait = NounTrait.SECOND_PERSON_PLURAL; 754 break; 755 default: 756 trait = NounTrait.GROUP; 757 } 758 } 759 finisher = finish; 760 761 } 762 } 763 public static void appendCapitalized(String s, TextBuffer dest) 764 { 765 dest.append(Character.toUpperCase(s.charAt(0))); 766 if(s.length() > 1) 767 dest.append(s.substring(1)); 768 } 769 @Override 770 public void appendSubstitution(MatchResult match, TextBuffer dest) { 771 if(match.isCaptured("at_sign")) 772 { 773 dest.append(finisher ? "@" : "\\@"); 774 } 775 else if(match.isCaptured("caret_sign")) 776 { 777 dest.append(finisher ? "^" : "\\^"); 778 } 779 else if(match.isCaptured("dollar_sign")) 780 { 781 dest.append(finisher ? "$" : "\\$"); 782 } 783 else if(match.isCaptured("tilde_sign")) 784 { 785 dest.append(finisher ? "~" : "\\~"); 786 } 787 else if(match.isCaptured("name")) 788 { 789 dest.append(trait.nameText(term)); 790 } 791 else if(match.isCaptured("Name")) 792 { 793 appendCapitalized(trait.nameText(term), dest); 794 } 795 else if(match.isCaptured("name_s")) 796 { 797 dest.append(trait.name_sText(term)); 798 } 799 else if(match.isCaptured("Name_s")) 800 { 801 appendCapitalized(trait.name_sText(term), dest); 802 } 803 else if(match.isCaptured("i")) 804 { 805 dest.append(trait.iText()); 806 } 807 else if(match.isCaptured("I")) 808 { 809 appendCapitalized(trait.iText(), dest); 810 } 811 else if(match.isCaptured("me")) 812 { 813 dest.append(trait.meText()); 814 } 815 else if(match.isCaptured("Me")) 816 { 817 appendCapitalized(trait.meText(), dest); 818 } 819 else if(match.isCaptured("my")) 820 { 821 dest.append(trait.myText()); 822 } 823 else if(match.isCaptured("My")) 824 { 825 appendCapitalized(trait.myText(), dest); 826 } 827 else if(match.isCaptured("mine")) 828 { 829 dest.append(trait.mineText()); 830 } 831 else if(match.isCaptured("Mine")) 832 { 833 appendCapitalized(trait.mineText(), dest); 834 } 835 else if(match.isCaptured("myself")) 836 { 837 dest.append(trait.myselfText()); 838 } 839 else if(match.isCaptured("Myself")) 840 { 841 appendCapitalized(trait.myselfText(), dest); 842 } 843 else if(match.isCaptured("s")) 844 { 845 dest.append(trait.sText()); 846 } 847 else if(match.isCaptured("ss")) 848 { 849 dest.append(trait.ssText()); 850 } 851 else if(match.isCaptured("sss")) 852 { 853 dest.append(trait.sssText()); 854 } 855 else if(match.isCaptured("usi")) 856 { 857 dest.append(trait.usiText()); 858 } 859 else if(match.isCaptured("fves")) 860 { 861 dest.append(trait.fvesText()); 862 } 863 else if(match.isCaptured("$")) 864 { 865 dest.append(trait.$Text()); 866 } 867 else if(match.isCaptured("$$")) 868 { 869 dest.append(trait.$$Text()); 870 } 871 else if(match.isCaptured("$$$")) 872 { 873 dest.append(trait.$$$Text()); 874 } 875 else if(match.isCaptured("other")) 876 { 877 String[] others = irregular.get(match.group("other")); 878 if(others != null && others.length == 11) 879 dest.append(others[trait.ordinal()]); 880 else 881 match.getGroup(0, dest); 882 } 883 else if(match.isCaptured("Other")) 884 { 885 String[] others = irregular.get(match.group("Other").toLowerCase()); 886 if(others != null && others.length == 11) 887 appendCapitalized(others[trait.ordinal()], dest); 888 else 889 match.getGroup(0, dest); 890 } 891 else if(match.isCaptured("direct")) 892 { 893 dest.append(trait.directText(term)); 894 } 895 else if(match.isCaptured("Direct")) 896 { 897 appendCapitalized(trait.directText(term), dest); 898 } 899 else 900 match.getGroup(0, dest); 901 } 902 } 903}