blob: 8400d1e788f2829420a735286cde5e818a87a348 [file] [log] [blame]
/*
*******************************************************************************
* Copyright (C) 1996-2000, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*
* $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/tools/normalizer/Attic/NormalizerBuilder.java,v $
* $Date: 2001/03/15 23:36:02 $
* $Revision: 1.12 $
*
*****************************************************************************************
*/
package com.ibm.tools.normalizer;
import java.io.*;
import java.util.*;
import com.ibm.text.*;
import com.ibm.util.CompactByteArray;
import com.ibm.util.CompactCharArray;
import com.ibm.util.Utility;
import com.ibm.icu.internal.UInfo;
public final class NormalizerBuilder
{
public static void main(String args[]) throws IOException {
try {
NormalizerBuilder foo = new NormalizerBuilder(args);
} catch (Throwable e) {
System.err.println(e.getLocalizedMessage());
e.printStackTrace();
System.in.read();
}
}
private UInfo uinfo;
/**
* Map char->String. Each entry maps a character with a
* decomposition (either canonical or compatibility) to that
* decomposition. The decomposition is in canonical order.
*/
private DecompMap decomps = new DecompMap();
/**
* Map of characters whose full canonical decomposition is
* DIFFERENT from their full compatibility decomposition.
*/
private DecompMap explodeCompat = new DecompMap();
/**
* Map of characters with a decomposition that are neither
* in explodeCompat nor in permutedCompositions.
*/
private DecompMap explodeOnly = new DecompMap();
/**
* Map of String->char of permutations that compose to a
* character. This does not include singletons or other
* composition exclusions. It is an inverse list, with valid
* permutations, for canonical decomposition.
*/
private CompMap permutedCompositions = new CompMap();
private CompMap binaryCompositions = new CompMap();
/**
* A set of characters that form the base of a combining
* sequence.
*/
private CharSet bases = new CharSet();
/**
* A set of characters that form the combining character of
* a combining sequence.
*/
private CharSet combining = new CharSet();
private Map pairExplosions = new HashMap();
private boolean fVerbose = false;
private boolean fWriteData = false;
private boolean fShowSizes = false;
private boolean fPrompt = false;
private boolean fJava = true;
private boolean fCPP = false;
private String fOutDir = null; // output directory for either Java or C++
/**
* The highest Unicode character that has a canonical
* decomposition. (i.e. largest char that can result from a
* primary canonical composition.) This is the largest char in
* permutedCompositions.
*/
char largestChar = 0;
public NormalizerBuilder(String[] args) throws IOException
{
// Parse my command line
for (int i = 0; i < args.length; i++)
{
if (args[i].equals("-data")) {
uinfo = new UInfo(args[++i], args[++i]);
}
else if (args[i].equals("-write")) {
fWriteData = true;
}
else if (args[i].equals("-verbose") || args[i]. equals("-v")) {
fVerbose = true;
}
else if (args[i].equals("-size")) {
fShowSizes = true;
}
else if (args[i].equals("-prompt")) {
fPrompt = true;
}
else if (args[i].equals("-java")) {
fJava = true;
fCPP = false;
}
else if (args[i].equals("-cpp")) {
fCPP = true;
fJava = false;
}
else if (args[i].equals("-outdir")) {
fOutDir = args[++i];
}
}
if (uinfo == null) {
uinfo = new UInfo();
}
if (fOutDir == null) {
fOutDir = fJava ? "src/com/ibm/text/"
: "./";
}
if (!fOutDir.endsWith("/")) { fOutDir += '/'; }
boolean canonicalOnly = true;
// Build decomps, a char->String mapping of characters to their
// decompositions, either canonical or compatibility.
createDecompositions();
outv("\nGenerating permuted compositions...");
// Form the list of all the permuted sequences that are
// canonically equivalent to the canonical decompositions. As
// a by-product, find out which are not combining character
// sequences.
for (char ch = 0; ch < 0xFFFF; ch++) {
String decomp = decomps.get(ch);
if (decomp != null) {
boolean done = false;
if (!uinfo.getFullDecomposition(ch,true).equals(
uinfo.getFullDecomposition(ch,false)))
{
explodeCompat.put(ch, uinfo.getFullDecomposition(ch, false));
done = true;
}
// It's always a combining base sequence, so removed last check - liu
if (uinfo.hasCanonicalDecomposition(ch) && decomp.length() > 1
&& !uinfo.isExcludedComposition(ch) /*&& uinfo.isCBS(decomp)*/)
{
if (decomp.length() <= 2) {
permutedCompositions.put(decomp, ch);
}
else {
/* Create a comprehensive list of
* permutations. Assume the first char is a
* base char, so don't permute it into the
* middle of the string -- just concatenate it
* onto the front. However, there may be
* embedded base characters, so we do a
* further check for canonical decomposition
* equivalence below. */
List alternatives = concat(decomp.charAt(0),
jumble(decomp.substring(1, decomp.length())));
for (int i = 0; i < alternatives.size(); ++i)
{
String variant = (String)alternatives.get(i);
String normalized = uinfo.fixCanonical(variant);
if (normalized.equals(decomp)) {
permutedCompositions.put(variant, ch);
}
}
}
largestChar = ch;
done = true;
}
if (!done) {
explodeOnly.put(ch, decomp); // Disparaged
}
}
}
outv("\nLargest composed char: " + Utility.hex(largestChar));
// Form the binary compositions
outv("\nGenerating pairwise compositions...");
Iterator list = permutedCompositions.keySet().iterator();
while (list.hasNext()) {
String decomp = (String)list.next();
char ch = permutedCompositions.get(decomp);
if (decomp.length() > 2) {
//
// If this is a composition of more than two characters,
// see if its initial portion is also a composition. If so, that lets
// us build up this composed character iteratively.
//
for (int i = decomp.length()-1; i > 1; --i) {
String partial = decomp.substring(0,i);
char partialMap = permutedCompositions.get(partial);
if (partialMap != 0) {
decomp = partialMap + decomp.substring(i);
break;
}
}
}
if (decomp.length() <= 2) {
binaryCompositions.put(decomp, ch);
} else {
//
// The composition takes more than two characters, and there's
// no way to build it up from smaller ones.
//
if (decomp.equals(uinfo.fixCanonical(decomp)))
{
// If the decomp is in canonical order, we're in trouble,
// since that means there's no way to generate this composed
// character from its canonically decomposed equivalent.
err("No pairwise compose of " + Utility.hex(decomp) +
" > " + Utility.hex(ch) + " " + uinfo.getName(ch,true) );
}
else {
// If the decomp is *not* in canonical order, it's not as
// bad, since composition will still work as long as
warn("No pairwise compose of non-canon " + Utility.hex(decomp) +
" > " + Utility.hex(ch) + " " + uinfo.getName(ch,true) );
}
}
bases.add(decomp.charAt(0));
// add to list of all combining characters in composites
for (int q = 1; q < decomp.length(); ++q) {
combining.add(decomp.charAt(q));
}
}
// Generate the pairwise explosions, where a composed char + combining char
// transforms into a different pair of characters, usually because the
// canonical combining classes are reversed.
outv("\nGenerating exploding pairs....");
List binaryValues = new ArrayList(binaryCompositions.values());
Collections.sort(binaryValues);
for (char addOn = 0; addOn < 0xFFFF; addOn++) {
if (combining.contains(addOn))
{
list = binaryValues.iterator();
while (list.hasNext()) {
MutableChar unichar = (MutableChar)list.next();
String chStr = String.valueOf(unichar.value);
String source = chStr + addOn;
String comp = binaryComposition(source);
if (comp.length() == 1) continue; // don't care if combines
if (comp.charAt(0) == addOn || comp.charAt(1) == addOn) continue; // rearranges
if (!source.equals(comp)) {
String decomp = fullDecomposition(source);
pairExplosions.put(source,comp);
bases.add(unichar);
}
}
}
}
buildDecompData();
buildComposeData();
outv("Success!");
if (fPrompt) {
System.out.println("\nHit any key to continue...");
System.in.read();
}
}
public String fullDecomposition(String s) {
return fullDecomposition(s, new StringBuffer()).toString();
}
public StringBuffer fullDecomposition(char ch, StringBuffer output) {
String value = decomps.get(ch);
if (value == null) {
bubbleAppend(output, ch);
}
else {
bubbleAppend(output, value);
}
return output;
}
public StringBuffer fullDecomposition(String s, StringBuffer output) {
for (int i = 0; i < s.length(); ++i) {
fullDecomposition(s.charAt(i),output);
}
return output;
}
public String binaryComposition(String sr) {
// set up decomposed string, init variables
StringBuffer output = new StringBuffer();
StringBuffer decomp = new StringBuffer();
if (sr.length() == 0) return output.toString();
// First generate the full decomposition of the input string
fullDecomposition(sr, decomp);
int basePosition = 0;
char base = decomp.charAt(0);
output.append(base);
// handle degenerate case--no base character at start
if (uinfo.getCanonicalClass(base) != 0) {
// later
}
// loop through, composing items with base
for (int i = 1; i < decomp.length(); ++i) {
char ch = decomp.charAt(i);
short can = uinfo.getCanonicalClass(ch);
char value = binaryCompositions.get(String.valueOf(base) + ch);
if (value != 0 && noObstructions(output, basePosition, can)) {
base = value;
output.setCharAt(basePosition, base);
} else if (can == 0) {
basePosition = output.length();
base = ch;
output.append(ch);
} else {
bubbleAppend(output, ch, can);
}
}
return output.toString();
}
public boolean noObstructions(StringBuffer buffer, int pos, short can) {
for (int j = buffer.length()-1; j > pos; --j) {
if (can == uinfo.getCanonicalClass(buffer.charAt(j))) {
return false;
}
}
return true;
}
public void bubbleAppend(StringBuffer buffer, char ch, short can) {
for (int j = buffer.length()-1; j >= 0; --j) {
if (can >= uinfo.getCanonicalClass(buffer.charAt(j))) {
buffer.insert(j + 1, ch);
return;
}
}
buffer.insert(0, ch);
}
public void bubbleAppend(StringBuffer buffer, char ch) {
bubbleAppend(buffer, ch, uinfo.getCanonicalClass(ch));
}
public void bubbleAppend(StringBuffer buffer, String s) {
for (int i = 0; i < s.length(); ++i) {
bubbleAppend(buffer, s.charAt(i));
}
}
String getDecomposition(char ch) {
return decomps.get(ch);
}
/**
* Generate a Map of all decompositions in Unicode. The keys in
* the map are MutableChar objects, one for each character that
* has a decomposition. The values are String objects containing
* the full decomposition for the character, in canonical order.
*/
private void createDecompositions()
{
outv("\nGenerating Full decompositions...");
StringBuffer temp = new StringBuffer();
short compatCount=0, canonCount=0;
for (char ch = 0; ch < 0xFFFF; ++ch) {
if (ch >= '\u4E00' && ch <= '\uD7A3') continue; // skip ideos
short category = uinfo.getCategory(ch);
if (category == uinfo.UNASSIGNED) continue; //skip reserved
if (category == uinfo.CONTROL) continue;
if (category == uinfo.FORMAT) continue;
if (category == uinfo.PRIVATE_USE) continue;
if (category == uinfo.SURROGATE) continue;
boolean canon = uinfo.hasCanonicalDecomposition(ch);
boolean compat = uinfo.hasCompatibilityDecomposition(ch);
if (canon) canonCount++;
if (compat) compatCount++;
if (canon || compat) {
String decomp = uinfo.getFullDecomposition(ch, canon);
temp.setLength(0);
temp.append(decomp);
uinfo.fixCanonical(temp); // put into canonical order
decomps.put(ch, temp.toString() );
}
}
}
/**
* Modify a list in place by prepending the given character to all
* of its elements, which are assumed to be strings.
*/
static List concat(char ch, List a) {
for (int i = 0; i < a.size(); ++i) {
a.set(i, ch + (String)a.get(i));
}
return a;
}
/**
* Return a list of Strings for all possible permutations of the
* characters in the input string.
*/
static List jumble (String source)
{
ArrayList result = new ArrayList();
if (source.length() == 1) {
result.add(source);
} else for (int i = 0; i < source.length(); ++i) {
result.addAll( concat( source.charAt(i),
jumble(source.substring(0,i)
+ source.substring(i+1,source.length()))));
}
return result;
}
static final int STR_INDEX_SHIFT = 2;
static final int STR_LENGTH_MASK = 0x0003;
static final int DECOMP_RECURSE = 0x00008000;
static final int DECOMP_MASK = 0x00007FFF;
/**
* Generate a new "DecompData.java" that contains the CompactArray definitions
* used in the {@link Normalizer.DECOMPOSE} operation.
*/
void buildDecompData() throws IOException {
outv("\nGenerating DecompData.java....");
//
// For each Unicode character that has a decomposition, we put its
// fully-decomposed form at the end of the "contents" string, followed
// by a null, and we put its index in "contents" into the CompactArray.
// If it does not have a decomposition, we store a bogus index.
//
// We do this first for all of the compatibility decompositions, save
// the index in MAX_COMPAT, and then do it again for the canonical
// decompositions. When the array is used later, any character whose
// decomp has an index greater than MAX_COMPAT is a canonical decomp.
//
int canonIndex = 0;
int compatIndex = 0;
// Map from Unicode character to replacement string index
CompactCharArray offsets = new CompactCharArray((char)0);
// We also need a place to store the replacement strings. Add a char at
// the front so that "0" won't be the index of any of the replacement strings.
StringBuffer replace = new StringBuffer().append("\uffff");
for (char ch = 0; ch < 0xFFFF; ch++) {
if (uinfo.hasCompatibilityDecomposition(ch)) {
compatIndex = putLength(replace, decomps.get(ch), 0);
offsets.setElementAt(ch, (char)compatIndex);
}
}
// Add the canonical decomps. Their indices must be > compatIndex.
for (char ch = 0; ch < 0xFFFF; ch++) {
if (uinfo.hasCanonicalDecomposition(ch)) {
if (ch == 0x0f77) {
outv("0F77: decomps.get() = " + Utility.hex(decomps.get(ch)));
outv("0F77: fullDecomp = " + Utility.hex(uinfo.getFullDecomposition(ch,false)));
}
canonIndex = putLength(replace, decomps.get(ch), compatIndex);
// If this character's full compatibility decomposition is different from
// its canonical decomp, that means one of the characters in its
// canonical decomp itself has a compatibility decomp. To deal with this,
// we set a bit flag telling the decomposer to recurse on this character.
if (!uinfo.getFullDecomposition(ch,true).equals(uinfo.getFullDecomposition(ch,false))) {
offsets.setElementAt(ch, (char)(canonIndex | DECOMP_RECURSE));
} else {
offsets.setElementAt(ch, (char)canonIndex);
}
}
}
//
// Now generate another CompactArray containing the combining class of every
// character in Unicode
//
final byte BASE = 0;
CompactByteArray canonClasses = new CompactByteArray(BASE);
for (char ch = 0; ch < 0xFFFF; ch++) {
short canonClass = uinfo.getCanonicalClass(ch);
if (canonClass != 0) {
canonClasses.setElementAt(ch, (byte)canonClass);
}
}
// Finally, write the data out to a compilable Java source file
if (fJava) {
String f = fOutDir + "DecompData";
out("Writing " + f);
writeDecompData(new JavaWriter(f),
canonIndex, compatIndex, BASE, offsets, replace, canonClasses);
}
if (fCPP) {
String f = fOutDir + "dcmpdata";
out("Writing " + f + ".(cpp|h)");
writeDecompData(new CPPWriter(f, "DecompData"),
canonIndex, compatIndex, BASE, offsets, replace, canonClasses);
}
outv("Decomp data: MAX_CANONICAL = " + canonIndex + ", MAX_DECOMP = " + compatIndex);
if (fShowSizes) {
int offsetSize = offsets.getIndexArray().length * 2 + offsets.getValueArray().length * 2;
int canonSize = canonClasses.getIndexArray().length * 2 + canonClasses.getValueArray().length;
int replaceLength = replace.length();
outv("Total runtime size of decomp data is "
+ (offsetSize + canonSize + replaceLength));
outv(" offsets: " + offsetSize);
outv(" canonClasses: " + canonSize);
outv(" replace: " + replaceLength);
}
}
void writeDecompData(SourceWriter out, int maxCanon, int maxCompat, short BASE,
CompactCharArray offsets, StringBuffer contents,
CompactByteArray canonClasses)
{
out.write("MAX_CANONICAL", maxCanon );
out.write("MAX_COMPAT", maxCompat );
out.write("DECOMP_MASK", DECOMP_MASK );
out.write("DECOMP_RECURSE", DECOMP_RECURSE );
out.write("BASE", BASE );
out.write("offsets", offsets );
out.write("contents", contents );
out.write("canonClass", canonClasses );
out.close();
}
//==========================================================================================
// Methods for generating and writing the composition data
//
final int TYPE_MASK = 0x0007;
final int INDEX_MASK = 0xFFF8;
final int INDEX_SHIFT = 3;
// MAX_BASES is used to map a 2-diminsional (base,combining) index pair onto a
// one-dimensional CompactArray. We could just use baseCount, but making it a power
// of two allows slightly better compaction.
final int MAX_BASES = 1024; // Product must be <= 64K
final int MAX_COMBINE = 65536/MAX_BASES;
final char // for character types
IGNORE = 0,
BASE = 1,
EXPLODING_BASE = 2,
COMBINING = 3,
INITIAL_JAMO = 4,
MEDIAL_JAMO = 5,
FINAL_JAMO = 6,
HANGUL = 7;
// These variables actually hold the composition data.
short baseCount = 1; // Leave 0 as an invalid index
short combineCount = 1; // Leave 0 as an invalid index
short nccCount = 0;
int maxCompat = 0;
int maxCanon = 0;
// This array contains types (from the set above) and indices into the "replace"
// and "actions" arrays
CompactCharArray lookup = new CompactCharArray(IGNORE);
// We also need a place to store the strings that result from replacements,
// explosions, and combinations. Add a char at the front so that "0" won't
// be the index of any of the replacement strings.
StringBuffer replace = new StringBuffer().append(" ");
// We need to represent each canonical character class as a single bit
// so that we can OR together a mask of all combining char classes seen
// Build an array that maps from combining class to a compacted integer
// from 0..n-1, where n is the number of distinct combining classes.
// E.g., in 3.0, there are 53 distinct combining classes.
int[] classMap = new int[256];
int[] typeBit;
// Build a two-dimensional array of the action to take for each base/combining pair
CompactCharArray actions = new CompactCharArray((char)0);
char[] actionIndex;
/**
* Generate a new "ComposeData.java" that contains the CompactArray definitions
* used in the {@link Normalizer.COMPOSE} operation.
*/
void buildComposeData() throws IOException
{
outv("\nGenerating ComposeData.java....");
BitSet usedIndices = new BitSet();
CharSet explodingBases = new CharSet();
NonComposingCombiningMap nccMap = new NonComposingCombiningMap();
// Find all characters that are both bases *and* have compatibility
// decompositions. These are weird
for (char ch = 0; ch < 0xFFFF; ch++) {
if (bases.contains(ch) && uinfo.hasCompatibilityDecomposition(ch)) {
//
// Add this character's explosion to the replacement string list.
// We're going to make sure that its "base index", i.e. the
// index for it in the actions array, is the same as the
// explosion's index in the replace string. This lets
// us use the same index for the character's two behaviors
//
int index = put(replace, explodeCompat.get(ch), 0);
outv(Utility.hex(ch) + " is base and has compat explosion "
+ Utility.hex(explodeCompat.get(ch)) );
addChar(lookup, ch, EXPLODING_BASE, index);
usedIndices.set(index);
explodingBases.add(ch);
}
}
// First add the base characters to the array.
// At the same time, compute their indices.
// Leave an empty base index of 0 as a placeholder for null operations.
//
for (char ch = 0; ch < 0xFFFF; ch++)
{
if (explodingBases.contains(ch)) {
continue;
}
short cclass = uinfo.getCanonicalClass(ch);
if (bases.contains(ch)) {
// Make sure that we don't use a base index that was already used
// for an exploding base character.
while (usedIndices.get(baseCount)) {
baseCount++;
}
// Now add the character to lookup as a base
addChar(lookup, ch, BASE, baseCount++);
}
if (combining.contains(ch)) {
classMap[cclass] = 1; // Mark this combining class as being used
addChar(lookup, ch, COMBINING, combineCount++);
}
if (ch >= '\u1100' && ch < '\u1160') {
addChar(lookup, ch, INITIAL_JAMO, 0);
}
if (ch >= '\u1161' && ch < '\u11a6') {
addChar(lookup, ch, MEDIAL_JAMO, 0);
}
if (ch >= '\u11a7' && ch < '\u11fa') {
addChar(lookup, ch, FINAL_JAMO, 0);
}
if (ch >= 0xac00 && ch <= 0xd7a4) {
addChar(lookup, ch, HANGUL, 0);
}
// Add explosions for all compatibility decompositions,
// including the Jamo --> Conjoining Jamo decomps.
// If the canonical decomposition is exactly one character
// one (4 hex digits) then we deal with it separately below.
if (explodeCompat.contains(ch) &&
uinfo.getDecomposition(ch).length() != 4)
{
maxCompat = put(replace, explodeCompat.get(ch), 0);
addExplosion(lookup, ch, maxCompat);
}
}
// Now add the explosions resulting from canonical decompositions
// These will all have indices greater than "maxCompat" so we can distinguish them.
//
for (char ch = 0; ch < 0xFFFF; ch++) {
short cclass = uinfo.getCanonicalClass(ch);
String explosion = null;
if (explodeOnly.contains(ch) && uinfo.hasCanonicalDecomposition(ch)) {
maxCanon = put(replace, explodeOnly.get(ch), maxCompat);
addExplosion(lookup, ch, maxCanon);
}
// else if (!combining.contains(ch) && cclass != 0 && classMap[cclass] != 0) {
// //
// // If a combining character didn't happen to end up in one of
// // the pairwise combinations or explosions we use but still has
// // a combining class that is the same as a character we *do* use,
// // we need to save its class so that we don't combine things "past" it.
// //
// // However, if the character has an explosion we *don't* need it, because
// // we'll never see it, only the results of its explosion.
// //
// addChar(lookup, ch, COMBINING, 0);
// nccCount++;
// }
// I'm rewriting this logic. Having an index of zero means that
// the typeBit[index] gets overwritten with multiple different
// values. So we must use real index values that are unique
// per combining class. Also, it doesn't matter if the class
// has been seen or not; we still need to record the character
// in order to have its type and class during composition.
else if (!combining.contains(ch) && cclass != 0) {
// If a combining character didn't happen to end up in one of
// the pairwise combinations or explosions we use but still has
// a combining class that is the same as a character we *do* use,
// we need to save its class.
// As our index, use combineCount and up. Reuse values by
// mapping them through nccMap, which keeps track of previously
// used values and allocates new ones only as needed, starting
// with zero. - Liu
classMap[cclass] = 1; // Mark this combining class as being used
addChar(lookup, ch, COMBINING, combineCount + nccMap.getIndexFor(cclass));
}
}
nccCount = (short) nccMap.getIndexCount(); // Liu
// Remap characters that have a canonical decomposition to a singleton,
// and also different compatibility and canonical full decompositions
// (that is, also are members of explodeCompat). These characters can't
// be exploded to their full decomposition since that breaks canonical
// composition (normalization form C). Instead, we place their
// singleton decomposition in the table, at the end. This works because
// the singleton will get recursively exploded by Normalizer. As of
// Unicode 3.0, this fix applies to U+1FFE, 1FFD, 2000, and 2001. - Liu
int singleton = replace.length();
for (char ch = 0; ch < 0xFFFF; ch++) {
if (!explodingBases.contains(ch) &&
explodeCompat.contains(ch) &&
uinfo.getDecomposition(ch).length() == 4) {
// There might be a cleaner way to do this, perhaps by folding
// this logic into the code above (perhaps calling
// addExplosion() instead of addChar()), but I couldn't find it.
char remap = (char)
Integer.parseInt(uinfo.getDecomposition(ch), 16);
int index = put(replace, String.valueOf(remap), singleton);
addChar(lookup, ch, EXPLODING_BASE, index);
outv("Canonical singleton " + Utility.hex(ch) +
" remaps to " + Utility.hex(remap) + " index=" + index);
}
}
// Now run through the combining classes again and assign bit numbers
// in the same ascending order as the canonical classes
int maskShift = 0;
int bit = 0;
for (int i = 0; i < 256; i++) {
if (classMap[i] != 0) {
classMap[i] = ++bit;
}
}
if (bit >= 64) {
err(String.valueOf(bit+1) + " combining classes; max is 64");
}
outv("# of combining classes is " + (bit+1));
outv("baseCount=" + baseCount + ", combineCount=" + combineCount
+ ", nccCount=" + nccCount);
if (baseCount > MAX_BASES) {
err(Integer.toString(baseCount) + " bases, limit is " + MAX_BASES);
err(Integer.toString(combineCount) + " combining chars, limit is " + MAX_COMBINE);
}
// Now build the "actions" array that tells what to do when each base /
// combining pair is seen.
//
// First do character pairs that combine into a single character...
//
Iterator iter = binaryCompositions.keySet().iterator();
while (iter.hasNext()) {
String source = (String)iter.next();
char ch = binaryCompositions.get(source);
int baseIndex = lookup.elementAt(source.charAt(0)) >>> INDEX_SHIFT;
int combiningIndex = lookup.elementAt(source.charAt(1)) >>> INDEX_SHIFT;
actions.setElementAt((char)(baseIndex + MAX_BASES*combiningIndex), ch);
}
//
// Pair explosions: base/combining pairs that explode into something else
// We're squeezing the indices for these in between MAX_COMPOSED and 0xFFFF,
// which means they can't be indexes into the "replace" string; those are too big.
// Instead they're indexes into the "actionIndex" array, which in turn contains
// indices in "replace"
//
actionIndex = new char[ pairExplosions.size() ];
short index = 0;
iter = pairExplosions.keySet().iterator();
while (iter.hasNext()) {
String source = (String)iter.next();
char base = source.charAt(0);
char combining = source.charAt(1);
int strIndex = put(replace, (String)pairExplosions.get(source), 0);
actionIndex[index] = (char)strIndex;
int baseIndex = lookup.elementAt(base) >>> INDEX_SHIFT;
int combiningIndex = lookup.elementAt(combining) >>> INDEX_SHIFT;
actions.setElementAt((char)(baseIndex + MAX_BASES*combiningIndex),
(char)(index + largestChar));
index++;
}
// Fill in the array that maps from combining class value
// to a bit numbe representing the canonical combining class.
// That is, map from 0..240 (in 3.0) to 0..52.
typeBit = new int[combineCount + nccCount];
for (char ch = 0; ch < 0xFFFF; ch++) {
int value = lookup.elementAt(ch);
int type = value & TYPE_MASK;
if (type == COMBINING) {
int ind = value >>> INDEX_SHIFT;
int cclass = uinfo.getCanonicalClass(ch);
if (typeBit[ind] != 0 && typeBit[ind] != classMap[cclass]) {
err("Overwriting typeBit[" + ind + "], was " +
typeBit[ind] + ", changing to " + classMap[cclass] + " for class " + cclass);
}
typeBit[ind] = classMap[cclass];
}
}
if (fJava) {
String f = fOutDir + "ComposeData";
out("Writing " + f);
writeComposeData(new JavaWriter(f));
}
if (fCPP) {
String f = fOutDir + "compdata";
out("Writing " + f + ".(cpp|h)");
writeComposeData(new CPPWriter(f, "ComposeData"));
}
if (fShowSizes) {
int lookupSize = lookup.getIndexArray().length * 2 + lookup.getValueArray().length * 2;
int actionSize = actions.getIndexArray().length * 2 + actions.getValueArray().length * 2;
int actIndexSize = actionIndex.length * 2;
int replaceSize = replace.length();
int typeBitSize = typeBit.length * 2;
outv("Total runtime size of compose data is "
+ (lookupSize + actionSize + actIndexSize + replaceSize + typeBitSize));
outv(" lookup: " + lookupSize);
outv(" actions: " + actionSize);
outv(" actionIndex: " + actIndexSize);
outv(" typeBit: " + typeBitSize);
outv(" replace: " + replaceSize);
}
}
void writeComposeData(SourceWriter out) {
out.write("BASE_COUNT", baseCount);
out.write("COMBINING_COUNT", combineCount);
out.write("MAX_COMPAT", maxCompat);
out.write("MAX_CANONICAL", maxCanon);
out.writeHex("MAX_COMPOSED", largestChar);
int maxIndex = replace.length();
out.write("MAX_INDEX", maxIndex );
out.write("INITIAL_JAMO_INDEX", maxIndex + 1);
out.write("MEDIAL_JAMO_INDEX", maxIndex + 2);
out.write("MAX_BASES", MAX_BASES );
out.write("MAX_COMBINE", MAX_COMBINE);
out.writeHex("TYPE_MASK", TYPE_MASK);
out.write("INDEX_SHIFT", INDEX_SHIFT);
// The character types
out.write("IGNORE", (int)IGNORE);
out.write("BASE", (int)BASE);
out.write("NON_COMPOSING_COMBINING", (int)EXPLODING_BASE);
out.write("COMBINING", (int)COMBINING);
out.write("INITIAL_JAMO", (int)INITIAL_JAMO);
out.write("MEDIAL_JAMO", (int)MEDIAL_JAMO);
out.write("FINAL_JAMO", (int)FINAL_JAMO);
out.write("HANGUL", (int)HANGUL);
out.write("lookup", lookup );
out.write("actions", actions );
out.write("actionIndex", actionIndex );
out.write("replace", replace );
out.write("typeBit", typeBit);
out.close();
}
void addChar(CompactCharArray lookup, char ch, int type, int index)
{
// First make sure it's not already present
if (lookup.elementAt(ch) != IGNORE)
{
char oldValue = lookup.elementAt(ch);
err(typeName(type) + " char is also "
+ typeName(oldValue & TYPE_MASK) + ": "
+ Utility.hex(ch) + " " + uinfo.getName(ch,true));
}
else if ((index << INDEX_SHIFT) > 65536) {
err("not enough bits: index " + index + " << INDEX_SHIFT = " + (index << INDEX_SHIFT));
} else {
lookup.setElementAt(ch, (char)(type | (index << INDEX_SHIFT)));
}
}
void addExplosion(CompactCharArray lookup, char ch, int index)
{
// First make sure it doesn't already have an index
char oldValue = lookup.elementAt(ch);
int oldIndex = oldValue >>> INDEX_SHIFT;
if (oldValue != IGNORE) {
err("Exploding char is already " + typeName(oldValue & TYPE_MASK)
+ " (index " + oldIndex + "): "
+ Utility.hex(ch) + " " + uinfo.getName(ch,true));
}
if (oldIndex != 0) {
err("Exploding char is already " + typeName(oldValue & TYPE_MASK)
+ " (index " + oldIndex + "): "
+ Utility.hex(ch) + " " + uinfo.getName(ch,true));
}
else if ((index << INDEX_SHIFT) > 65536) {
err("not enough bits: index " + index + " << INDEX_SHIFT = " + (index << INDEX_SHIFT));
} else {
lookup.setElementAt(ch, (char)((oldValue & ~INDEX_MASK) | (index << INDEX_SHIFT)));
}
}
String typeName(int type) {
switch (type) {
case IGNORE: return "Ignored";
case BASE: return "Base";
case EXPLODING_BASE: return "Exploding Base";
case COMBINING: return "Combining";
case INITIAL_JAMO: return "Initial Jamo";
case MEDIAL_JAMO: return "Medial Jamo";
case FINAL_JAMO: return "Final Jamo";
case HANGUL: return "Hangul";
default: return "Unknown";
}
}
static final int put(StringBuffer buf, String str, int minIndex)
{
str = str + '\u0000'; // Add trailing null
int index = buf.toString().indexOf(str);
if (index <= minIndex) {
index = buf.length();
buf.append(str);
}
return index;
}
static final int putLength(StringBuffer buf, String str, int minIndex) {
int length = str.length();
if (length >= (1 << STR_INDEX_SHIFT)) {
// There's no room to store the length in the index, so
// add a null terminator and use a 0 length to flag this
str = str + '\u0000';
length = 0;
}
int index = buf.toString().indexOf(str);
if (index <= minIndex) {
index = buf.length();
buf.append(str);
}
return (index << STR_INDEX_SHIFT) | length;
}
//--------------------------------------------------------------------------------
// Output & formatting
void out(String str) {
System.out.println(str);
}
void outv(String str) {
if (fVerbose) System.out.println(str);
}
void warn(String str) {
System.err.println("Warning: " + str);
}
void err(String str) {
System.err.println("ERROR: " + str);
}
}
//-----------------------------------------------------------------------------
// Utility classes
//-----------------------------------------------------------------------------
class DecompMap extends HashMap {
public DecompMap() {
}
void put(char ch, String value) {
put(new MutableChar(ch), value);
}
String get(char ch) {
Object obj = get(probe.set(ch));
return (obj != null) ? (String)obj : null;
}
boolean contains(char ch) {
return containsKey(probe.set(ch));
}
MutableChar probe = new MutableChar(' ');
}
class CompMap extends HashMap {
public CompMap() {
}
void put(String key, char value) {
put(key, new MutableChar(value));
}
char get(String key) {
Object obj = get((Object)key);
return (obj != null) ? ((MutableChar)obj).value : 0;
}
}
class CharSet extends HashSet {
public CharSet() {
}
public void add(char ch) {
add(new MutableChar(ch));
}
public boolean contains(char ch) {
return contains(probe.set(ch));
}
MutableChar probe = new MutableChar(' ');
}
/**
* An int->int map. Each time a non-existent key is looked up,
* create a new mapping to the next available integer value.
*/
class NonComposingCombiningMap {
int index;
Hashtable hash;
public NonComposingCombiningMap() {
index = 0;
hash = new Hashtable();
}
/**
* Return the existing mapping of class. If no such mapping
* exists, create one and return it. New mappings map to
* zero, then one, etc.
*/
public int getIndexFor(int cclass) {
Integer cl = new Integer(cclass);
Integer ind = (Integer) hash.get(cl);
if (ind != null) {
return ind.intValue();
}
hash.put(cl, new Integer(index));
return index++;
}
/**
* Return the number of mappings made so far. That is, getIndexFor()
* has returned integers 0..getIndexCount()-1.
*/
public int getIndexCount() {
return index;
}
}