|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package java.lang; |
|
|
|
import java.text.BreakIterator; |
|
import java.util.HashSet; |
|
import java.util.Hashtable; |
|
import java.util.Iterator; |
|
import java.util.Locale; |
|
import sun.text.Normalizer; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final class ConditionalSpecialCasing { |
|
|
|
|
|
static final int FINAL_CASED = 1; |
|
static final int AFTER_SOFT_DOTTED = 2; |
|
static final int MORE_ABOVE = 3; |
|
static final int AFTER_I = 4; |
|
static final int NOT_BEFORE_DOT = 5; |
|
|
|
|
|
static final int COMBINING_CLASS_ABOVE = 230; |
|
|
|
|
|
static Entry[] entry = { |
|
//# ================================================================================ |
|
//# Conditional mappings |
|
//# ================================================================================ |
|
new Entry(0x03A3, new char[]{0x03C2}, new char[]{0x03A3}, null, FINAL_CASED), |
|
new Entry(0x0130, new char[]{0x0069, 0x0307}, new char[]{0x0130}, null, 0), |
|
|
|
//# ================================================================================ |
|
//# Locale-sensitive mappings |
|
//# ================================================================================ |
|
//# Lithuanian |
|
new Entry(0x0307, new char[]{0x0307}, new char[]{}, "lt", AFTER_SOFT_DOTTED), |
|
new Entry(0x0049, new char[]{0x0069, 0x0307}, new char[]{0x0049}, "lt", MORE_ABOVE), |
|
new Entry(0x004A, new char[]{0x006A, 0x0307}, new char[]{0x004A}, "lt", MORE_ABOVE), |
|
new Entry(0x012E, new char[]{0x012F, 0x0307}, new char[]{0x012E}, "lt", MORE_ABOVE), |
|
new Entry(0x00CC, new char[]{0x0069, 0x0307, 0x0300}, new char[]{0x00CC}, "lt", 0), |
|
new Entry(0x00CD, new char[]{0x0069, 0x0307, 0x0301}, new char[]{0x00CD}, "lt", 0), |
|
new Entry(0x0128, new char[]{0x0069, 0x0307, 0x0303}, new char[]{0x0128}, "lt", 0), |
|
|
|
//# ================================================================================ |
|
//# Turkish and Azeri |
|
new Entry(0x0130, new char[]{0x0069}, new char[]{0x0130}, "tr", 0), |
|
new Entry(0x0130, new char[]{0x0069}, new char[]{0x0130}, "az", 0), |
|
new Entry(0x0307, new char[]{}, new char[]{0x0307}, "tr", AFTER_I), |
|
new Entry(0x0307, new char[]{}, new char[]{0x0307}, "az", AFTER_I), |
|
new Entry(0x0049, new char[]{0x0131}, new char[]{0x0049}, "tr", NOT_BEFORE_DOT), |
|
new Entry(0x0049, new char[]{0x0131}, new char[]{0x0049}, "az", NOT_BEFORE_DOT), |
|
new Entry(0x0069, new char[]{0x0069}, new char[]{0x0130}, "tr", 0), |
|
new Entry(0x0069, new char[]{0x0069}, new char[]{0x0130}, "az", 0) |
|
}; |
|
|
|
|
|
static Hashtable<Integer, HashSet<Entry>> entryTable = new Hashtable<>(); |
|
static { |
|
|
|
for (Entry cur : entry) { |
|
Integer cp = cur.getCodePoint(); |
|
HashSet<Entry> set = entryTable.get(cp); |
|
if (set == null) { |
|
set = new HashSet<>(); |
|
entryTable.put(cp, set); |
|
} |
|
set.add(cur); |
|
} |
|
} |
|
|
|
static int toLowerCaseEx(String src, int index, Locale locale) { |
|
char[] result = lookUpTable(src, index, locale, true); |
|
|
|
if (result != null) { |
|
if (result.length == 1) { |
|
return result[0]; |
|
} else { |
|
return Character.ERROR; |
|
} |
|
} else { |
|
|
|
return Character.toLowerCase(src.codePointAt(index)); |
|
} |
|
} |
|
|
|
static int toUpperCaseEx(String src, int index, Locale locale) { |
|
char[] result = lookUpTable(src, index, locale, false); |
|
|
|
if (result != null) { |
|
if (result.length == 1) { |
|
return result[0]; |
|
} else { |
|
return Character.ERROR; |
|
} |
|
} else { |
|
|
|
return Character.toUpperCaseEx(src.codePointAt(index)); |
|
} |
|
} |
|
|
|
static char[] toLowerCaseCharArray(String src, int index, Locale locale) { |
|
return lookUpTable(src, index, locale, true); |
|
} |
|
|
|
static char[] toUpperCaseCharArray(String src, int index, Locale locale) { |
|
char[] result = lookUpTable(src, index, locale, false); |
|
if (result != null) { |
|
return result; |
|
} else { |
|
return Character.toUpperCaseCharArray(src.codePointAt(index)); |
|
} |
|
} |
|
|
|
private static char[] lookUpTable(String src, int index, Locale locale, boolean bLowerCasing) { |
|
HashSet<Entry> set = entryTable.get(src.codePointAt(index)); |
|
char[] ret = null; |
|
|
|
if (set != null) { |
|
Iterator<Entry> iter = set.iterator(); |
|
String currentLang = locale.getLanguage(); |
|
while (iter.hasNext()) { |
|
Entry entry = iter.next(); |
|
String conditionLang = entry.getLanguage(); |
|
if (((conditionLang == null) || (conditionLang.equals(currentLang))) && |
|
isConditionMet(src, index, locale, entry.getCondition())) { |
|
ret = bLowerCasing ? entry.getLowerCase() : entry.getUpperCase(); |
|
if (conditionLang != null) { |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
private static boolean isConditionMet(String src, int index, Locale locale, int condition) { |
|
switch (condition) { |
|
case FINAL_CASED: |
|
return isFinalCased(src, index, locale); |
|
|
|
case AFTER_SOFT_DOTTED: |
|
return isAfterSoftDotted(src, index); |
|
|
|
case MORE_ABOVE: |
|
return isMoreAbove(src, index); |
|
|
|
case AFTER_I: |
|
return isAfterI(src, index); |
|
|
|
case NOT_BEFORE_DOT: |
|
return !isBeforeDot(src, index); |
|
|
|
default: |
|
return true; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isFinalCased(String src, int index, Locale locale) { |
|
BreakIterator wordBoundary = BreakIterator.getWordInstance(locale); |
|
wordBoundary.setText(src); |
|
int ch; |
|
|
|
|
|
for (int i = index; (i >= 0) && !wordBoundary.isBoundary(i); |
|
i -= Character.charCount(ch)) { |
|
|
|
ch = src.codePointBefore(i); |
|
if (isCased(ch)) { |
|
|
|
int len = src.length(); |
|
|
|
for (i = index + Character.charCount(src.codePointAt(index)); |
|
(i < len) && !wordBoundary.isBoundary(i); |
|
i += Character.charCount(ch)) { |
|
|
|
ch = src.codePointAt(i); |
|
if (isCased(ch)) { |
|
return false; |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isAfterI(String src, int index) { |
|
int ch; |
|
int cc; |
|
|
|
|
|
for (int i = index; i > 0; i -= Character.charCount(ch)) { |
|
|
|
ch = src.codePointBefore(i); |
|
|
|
if (ch == 'I') { |
|
return true; |
|
} else { |
|
cc = Normalizer.getCombiningClass(ch); |
|
if ((cc == 0) || (cc == COMBINING_CLASS_ABOVE)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isAfterSoftDotted(String src, int index) { |
|
int ch; |
|
int cc; |
|
|
|
|
|
for (int i = index; i > 0; i -= Character.charCount(ch)) { |
|
|
|
ch = src.codePointBefore(i); |
|
|
|
if (isSoftDotted(ch)) { |
|
return true; |
|
} else { |
|
cc = Normalizer.getCombiningClass(ch); |
|
if ((cc == 0) || (cc == COMBINING_CLASS_ABOVE)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isMoreAbove(String src, int index) { |
|
int ch; |
|
int cc; |
|
int len = src.length(); |
|
|
|
|
|
for (int i = index + Character.charCount(src.codePointAt(index)); |
|
i < len; i += Character.charCount(ch)) { |
|
|
|
ch = src.codePointAt(i); |
|
cc = Normalizer.getCombiningClass(ch); |
|
|
|
if (cc == COMBINING_CLASS_ABOVE) { |
|
return true; |
|
} else if (cc == 0) { |
|
return false; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isBeforeDot(String src, int index) { |
|
int ch; |
|
int cc; |
|
int len = src.length(); |
|
|
|
|
|
for (int i = index + Character.charCount(src.codePointAt(index)); |
|
i < len; i += Character.charCount(ch)) { |
|
|
|
ch = src.codePointAt(i); |
|
|
|
if (ch == '\u0307') { |
|
return true; |
|
} else { |
|
cc = Normalizer.getCombiningClass(ch); |
|
if ((cc == 0) || (cc == COMBINING_CLASS_ABOVE)) { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static boolean isCased(int ch) { |
|
int type = Character.getType(ch); |
|
if (type == Character.LOWERCASE_LETTER || |
|
type == Character.UPPERCASE_LETTER || |
|
type == Character.TITLECASE_LETTER) { |
|
return true; |
|
} else { |
|
// Check for Other_Lowercase and Other_Uppercase |
|
|
|
if ((ch >= 0x02B0) && (ch <= 0x02B8)) { |
|
|
|
return true; |
|
} else if ((ch >= 0x02C0) && (ch <= 0x02C1)) { |
|
|
|
return true; |
|
} else if ((ch >= 0x02E0) && (ch <= 0x02E4)) { |
|
|
|
return true; |
|
} else if (ch == 0x0345) { |
|
|
|
return true; |
|
} else if (ch == 0x037A) { |
|
|
|
return true; |
|
} else if ((ch >= 0x1D2C) && (ch <= 0x1D61)) { |
|
|
|
return true; |
|
} else if ((ch >= 0x2160) && (ch <= 0x217F)) { |
|
// ROMAN NUMERAL ONE..ROMAN NUMERAL ONE THOUSAND |
|
|
|
return true; |
|
} else if ((ch >= 0x24B6) && (ch <= 0x24E9)) { |
|
// CIRCLED LATIN CAPITAL LETTER A..CIRCLED LATIN CAPITAL LETTER Z |
|
|
|
return true; |
|
} else { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
private static boolean isSoftDotted(int ch) { |
|
switch (ch) { |
|
case 0x0069: |
|
case 0x006A: |
|
case 0x012F: |
|
case 0x0268: |
|
case 0x0456: |
|
case 0x0458: |
|
case 0x1D62: |
|
case 0x1E2D: |
|
case 0x1ECB: |
|
case 0x2071: |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
static class Entry { |
|
int ch; |
|
char [] lower; |
|
char [] upper; |
|
String lang; |
|
int condition; |
|
|
|
Entry(int ch, char[] lower, char[] upper, String lang, int condition) { |
|
this.ch = ch; |
|
this.lower = lower; |
|
this.upper = upper; |
|
this.lang = lang; |
|
this.condition = condition; |
|
} |
|
|
|
int getCodePoint() { |
|
return ch; |
|
} |
|
|
|
char[] getLowerCase() { |
|
return lower; |
|
} |
|
|
|
char[] getUpperCase() { |
|
return upper; |
|
} |
|
|
|
String getLanguage() { |
|
return lang; |
|
} |
|
|
|
int getCondition() { |
|
return condition; |
|
} |
|
} |
|
} |