blob: f6363991938f663dba67d7d91a6391edad9d8294 [file] [log] [blame]
/**
*******************************************************************************
* Copyright (C) 2001-2006, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
package com.ibm.icu.dev.test.collator;
import java.util.Iterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.Vector;
import com.ibm.icu.dev.test.ModuleTest;
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.TestDataModule.DataMap;
import com.ibm.icu.impl.LocaleUtility;
import com.ibm.icu.impl.Utility;
import com.ibm.icu.lang.UCharacter;
import com.ibm.icu.text.CollationElementIterator;
import com.ibm.icu.text.CollationKey;
import com.ibm.icu.text.Collator;
import com.ibm.icu.text.RawCollationKey;
import com.ibm.icu.text.RuleBasedCollator;
import com.ibm.icu.text.UTF16;
public class CollationTest extends ModuleTest{
// public methods --------------------------------------------------------
public static void main(String[] args) throws Exception{
new CollationTest().run(args);
}
public CollationTest() {
super("com/ibm/icu/dev/data/testdata/", "DataDrivenCollationTest");
}
public void processModules() {
for (Iterator iter = t.getSettingsIterator(); iter.hasNext();) {
DataMap setting = (DataMap) iter.next();
processSetting(setting);
}
}
// package private methods ----------------------------------------------
static void doTest(TestFmwk test, RuleBasedCollator col, String source,
String target, int result)
{
doTestVariant(test, col, source, target, result);
if (result == -1) {
doTestVariant(test, col, target, source, 1);
}
else if (result == 1) {
doTestVariant(test, col, target, source, -1);
}
else {
doTestVariant(test, col, target, source, 0);
}
CollationElementIterator iter = col.getCollationElementIterator(source);
backAndForth(test, iter);
iter.setText(target);
backAndForth(test, iter);
}
/**
* Return an integer array containing all of the collation orders
* returned by calls to next on the specified iterator
*/
static int[] getOrders(CollationElementIterator iter)
{
int maxSize = 100;
int size = 0;
int[] orders = new int[maxSize];
int order;
while ((order = iter.next()) != CollationElementIterator.NULLORDER) {
if (size == maxSize) {
maxSize *= 2;
int[] temp = new int[maxSize];
System.arraycopy(orders, 0, temp, 0, size);
orders = temp;
}
orders[size++] = order;
}
if (maxSize > size) {
int[] temp = new int[size];
System.arraycopy(orders, 0, temp, 0, size);
orders = temp;
}
return orders;
}
static void backAndForth(TestFmwk test, CollationElementIterator iter)
{
// Run through the iterator forwards and stick it into an array
iter.reset();
int[] orders = getOrders(iter);
// Now go through it backwards and make sure we get the same values
int index = orders.length;
int o;
// reset the iterator
iter.reset();
while ((o = iter.previous()) != CollationElementIterator.NULLORDER) {
if (o != orders[--index]) {
if (o == 0) {
index ++;
} else {
while (index > 0 && orders[index] == 0) {
index --;
}
if (o != orders[index]) {
test.errln("Mismatch at index " + index + ": 0x"
+ Integer.toHexString(orders[index]) + " vs 0x" + Integer.toHexString(o));
break;
}
}
}
}
while (index != 0 && orders[index - 1] == 0) {
index --;
}
if (index != 0) {
String msg = "Didn't get back to beginning - index is ";
test.errln(msg + index);
iter.reset();
test.err("next: ");
while ((o = iter.next()) != CollationElementIterator.NULLORDER) {
String hexString = "0x" + Integer.toHexString(o) + " ";
test.err(hexString);
}
test.errln("");
test.err("prev: ");
while ((o = iter.previous()) != CollationElementIterator.NULLORDER) {
String hexString = "0x" + Integer.toHexString(o) + " ";
test.err(hexString);
}
test.errln("");
}
}
// private data members --------------------------------------------------
private String m_sequence_;
private int m_sequenceIndex_;
private String m_source_;
private StringBuffer m_target_ = new StringBuffer();
private int m_nextRelation_;
private int m_relation_;
// private methods -------------------------------------------------------
private void processSetting(DataMap settings) {
RuleBasedCollator col = null;
// ok i have to be careful here since it seems like we can have
// multiple locales for each test
String locale = settings.getString("TestLocale");
if (locale != null) {
// this is a case where we have locale
try {
Locale l = LocaleUtility.getLocaleFromName(locale);
col = (RuleBasedCollator)Collator.getInstance(l);
}catch (MissingResourceException e){
warnln("Could not load the locale data for locale " + locale);
}catch (Exception e) {
errln("Error creating collator for locale " + locale);
}
logln("Testing collator for locale " + locale);
processSetting2(settings, col);
}
String rules = settings.getString("Rules");
// ok i have to be careful here since it seems like we can have
// multiple rules for each test
if (rules != null) {
// here we deal with rules
try {
col = new RuleBasedCollator(rules);
}catch (MissingResourceException e){
warnln("Could not load the locale data: " + e.getMessage());
} catch (Exception e) {
errln("Error creating collator for rules " + rules);
}
processSetting2(settings, col);
}
}
private void processSetting2(DataMap settings,RuleBasedCollator col)
{
// ok i have to be careful here since it seems like we can have
// multiple rules for each test
String arguments = settings.getString("Arguments");
if (arguments != null) {
handleArguments(col, arguments);
}
processTestCases(col);
}
/**
* Reads the options string and sets appropriate attributes in collator
*/
private void handleArguments(RuleBasedCollator col, String argument) {
int i = 0;
boolean printInfo = false;
while (i < argument.length()) {
if (!UCharacter.isWhitespace(argument.charAt(i))) {
// eat whitespace
break;
}
i ++;
}
while (i < argument.length()) {
// skip opening '['
if (argument.charAt(i) == '[') {
i ++;
}
else {
if(!isModularBuild()){
errln("Error in collation arguments, missing ["); // no opening '['
}
// !!! following line has no effect
printInfo=true;
return;
}
int value = argument.indexOf(' ', i);
String option = argument.substring(i, value);
i = argument.indexOf(']', value);
String optionvalue = argument.substring(value + 1, i);
i ++;
// some options are not added because they have no public apis yet
// TODO add the rest of the options
if (option.equalsIgnoreCase("alternate")) {
if (optionvalue.equalsIgnoreCase("non-ignorable")) {
col.setAlternateHandlingShifted(false);
}
else {
col.setAlternateHandlingShifted(true);
}
}
else if (option.equals("strength")) {
if (optionvalue.equalsIgnoreCase("1")) {
col.setStrength(Collator.PRIMARY);
}
else if (optionvalue.equalsIgnoreCase("2")) {
col.setStrength(Collator.SECONDARY);
}
else if (optionvalue.equalsIgnoreCase("3")) {
col.setStrength(Collator.TERTIARY);
}
else if (optionvalue.equalsIgnoreCase("4")) {
col.setStrength(Collator.QUATERNARY);
}
}
}
if (printInfo) {
warnln("Could not load the locale data. Skipping...");
}
// !!! effect is odd, if no modular build, this emits no
// message at all. How come? Hmmm. printInfo is never
// true if we get here, so this code is never executed.
/*
if(printInfo == true && isModularBuild()){
infoln("Could not load the locale data. Skipping...");
}
*/
}
private void processTestCases(RuleBasedCollator col) {
for (Iterator iter = t.getDataIterator(); iter.hasNext();) {
DataMap e1 = (DataMap) iter.next();
processSequence(col, e1.getString("sequence"));
}
}
private void processSequence(RuleBasedCollator col, String sequence) {
// TODO: have a smarter tester that remembers the sequence and ensures
// that the complete sequence is in order. That is why I have made a
// constraint in the sequence format.
m_sequence_ = sequence;
m_sequenceIndex_ = 0;
m_nextRelation_ = -1;
m_target_.delete(0, m_target_.length());
Vector vector = new Vector();
int lastsmallerthanindex = -1;
getNextInSequence();
while (getNextInSequence()) {
String target = m_target_.toString();
doTest(this, col, m_source_, target, m_relation_);
int vsize = vector.size();
for (int i = vsize - 1; i >= 0; i --) {
String source = (String)vector.elementAt(i);
if (i > lastsmallerthanindex) {
doTest(this, col, source, target, m_relation_);
}
else {
doTest(this, col, source, target, -1);
}
}
vector.addElement(target);
if (m_relation_ < 0) {
lastsmallerthanindex = vsize - 1;
}
}
}
/**
* Parses the sequence to be tested
*/
private boolean getNextInSequence() {
if (m_sequenceIndex_ >= m_sequence_.length()) {
return false;
}
boolean quoted = false;
boolean quotedsingle = false;
boolean done = false;
int i = m_sequenceIndex_;
int offset = 0;
m_source_ = m_target_.toString();
m_relation_ = m_nextRelation_;
m_target_.delete(0, m_target_.length());
while (i < m_sequence_.length() && !done) {
int ch = UTF16.charAt(m_sequence_, i);
if (UCharacter.isSupplementary(ch)) {
i += 2;
}
else {
i ++;
}
if (!quoted) {
if (UCharacter.isWhitespace(ch)) {
continue;
}
switch (ch) {
case 0x003C : // <
m_nextRelation_ = -1;
done = true;
break;
case 0x003D : // =
m_nextRelation_ = 0;
done = true;
break;
case 0x003E : // >
m_nextRelation_ = 1;
done = true;
break;
case 0x0027 : // ' very basic quoting
quoted = true;
quotedsingle = false;
break;
case 0x005c : // \ single quote
quoted = true;
quotedsingle = true;
break;
default:
UTF16.insert(m_target_, offset, ch);
if (UCharacter.isSupplementary(ch)) {
offset += 2;
}
else {
offset ++;
}
}
}
else {
if (ch == 0x0027) {
quoted = false;
}
else {
UTF16.insert(m_target_, offset, ch);
if (UCharacter.isSupplementary(ch)) {
offset += 2;
}
else {
offset ++;
}
}
if (quotedsingle) {
quoted = false;
}
}
}
if (quoted == true) {
errln("Quote in sequence not closed!");
return false;
}
m_sequenceIndex_ = i;
return true;
}
private static void doTestVariant(TestFmwk test,
RuleBasedCollator myCollation,
String source, String target, int result)
{
boolean printInfo = false;
int compareResult = myCollation.compare(source, target);
if (compareResult != result) {
// !!! if not mod build, error, else nothing.
// warnln if not build, error, else always print warning.
// do we need a 'quiet warning?' (err or log). Hmmm,
// would it work to have the 'verbose' flag let you
// suppress warnings? Are there ever some warnings you
// want to suppress, and others you don't?
if(!test.isModularBuild()){
test.errln("Comparing \"" + Utility.hex(source) + "\" with \""
+ Utility.hex(target) + "\" expected " + result
+ " but got " + compareResult);
}else{
printInfo = true;
}
}
CollationKey ssk = myCollation.getCollationKey(source);
CollationKey tsk = myCollation.getCollationKey(target);
compareResult = ssk.compareTo(tsk);
if (compareResult != result) {
if(!test.isModularBuild()){
test.errln("Comparing CollationKeys of \"" + Utility.hex(source)
+ "\" with \"" + Utility.hex(target)
+ "\" expected " + result + " but got "
+ compareResult);
}else{
printInfo = true;
}
}
RawCollationKey srsk = new RawCollationKey();
myCollation.getRawCollationKey(source, srsk);
RawCollationKey trsk = new RawCollationKey();
myCollation.getRawCollationKey(target, trsk);
compareResult = ssk.compareTo(tsk);
if (compareResult != result) {
if(!test.isModularBuild()){
test.errln("Comparing RawCollationKeys of \""
+ Utility.hex(source)
+ "\" with \"" + Utility.hex(target)
+ "\" expected " + result + " but got "
+ compareResult);
}else{
printInfo = true;
}
}
// hmmm, but here we issue a warning
// only difference is, one warning or two, and detailed info or not?
// hmmm, does seem preferable to omit detail if we know it is due to missing resource data.
// well, if we label the errors as warnings, we can let people know the details, but
// also know they may be due to missing resource data. basically this code is asserting
// that the errors are due to missing resource data, which may or may not be true.
if (printInfo) {
test.warnln("Could not load locale data skipping.");
}
}
}