//##header J2SE15 /* ********************************************************************** * Copyright (c) 2006-2009, International Business Machines * Corporation and others. All Rights Reserved. ********************************************************************** * Created on 2006-4-21 */ package com.ibm.icu.dev.test; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.MissingResourceException; import java.util.NoSuchElementException; import com.ibm.icu.impl.ICUResourceBundle; import com.ibm.icu.util.UResourceBundle; import com.ibm.icu.util.UResourceBundleIterator; import com.ibm.icu.util.UResourceTypeMismatchException; /** * Represents a collection of test data described in a UResourceBoundle file. * * The root of the UResourceBoundle file is a table resource, and it has one * Info and one TestData sub-resources. The Info describes the data module * itself. The TestData, which is a table resource, has a collection of test * data. * * The test data is a named table resource which has Info, Settings, Headers, * and Cases sub-resources. * *
 * DataModule:table(nofallback){ 
 *   Info:table {} 
 *   TestData:table {
 *     entry_name:table{
 *       Info:table{}
 *       Settings:array{}
 *       Headers:array{}
 *       Cases:array{}
 *     }
 *   } 
 * }
 * 
* * The test data is expected to be fed to test code by following sequence * * for each setting in Setting{ * prepare the setting * for each test data in Cases{ * perform the test * } * } * * For detail of the specification, please refer to the code. The code is * initially ported from "icu4c/source/tools/ctestfw/unicode/tstdtmod.h" * and should be maintained parallelly. * * @author Raymond Yang */ class ResourceModule implements TestDataModule { private static final String INFO = "Info"; // private static final String DESCRIPTION = "Description"; // private static final String LONG_DESCRIPTION = "LongDescription"; private static final String TEST_DATA = "TestData"; private static final String SETTINGS = "Settings"; private static final String HEADER = "Headers"; private static final String DATA = "Cases"; UResourceBundle res; UResourceBundle info; UResourceBundle defaultHeader; UResourceBundle testData; ResourceModule(String baseName, String localeName) throws DataModuleFormatError{ res = (UResourceBundle) UResourceBundle.getBundleInstance(baseName, localeName); info = getFromTable(res, INFO, UResourceBundle.TABLE); testData = getFromTable(res, TEST_DATA, UResourceBundle.TABLE); try { // unfortunately, actually, data can be either ARRAY or STRING defaultHeader = getFromTable(info, HEADER, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING}); } catch (MissingResourceException e){ defaultHeader = null; } } public String getName() { return res.getKey(); } public DataMap getInfo() { return new UTableResource(info); } public TestData getTestData(String testName) throws DataModuleFormatError { return new UResourceTestData(defaultHeader, testData.get(testName)); } public Iterator getTestDataIterator() { return new IteratorAdapter(testData){ protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError { return new UResourceTestData(defaultHeader, nextRes); } }; } /** * To make UResourceBundleIterator works like Iterator * and return various data-driven test object for next() call * * @author Raymond Yang */ private abstract static class IteratorAdapter implements Iterator{ private UResourceBundle res; private UResourceBundleIterator itr; private Object preparedNextElement = null; // fix a strange behavior for UResourceBundleIterator for // UResourceBundle.STRING. It support hasNext(), but does // not support next() now. // // Use the iterated resource itself as the result from next() call private boolean isStrRes = false; private boolean isStrResPrepared = false; // for STRING resouce, we only prepare once IteratorAdapter(UResourceBundle theRes) { assert_not (theRes == null); res = theRes; itr = ((ICUResourceBundle)res).getIterator(); isStrRes = res.getType() == UResourceBundle.STRING; } public void remove() { // do nothing } private boolean hasNextForStrRes(){ assert_is (isStrRes); assert_not (!isStrResPrepared && preparedNextElement != null); if (isStrResPrepared && preparedNextElement != null) return true; if (isStrResPrepared && preparedNextElement == null) return false; // only prepare once assert_is (!isStrResPrepared && preparedNextElement == null); try { preparedNextElement = prepareNext(res); assert_not (preparedNextElement == null, "prepareNext() should not return null"); isStrResPrepared = true; // toggle the tag return true; } catch (DataModuleFormatError e) { //#if defined(FOUNDATION10) || defined(J2SE13) //## throw new RuntimeException(e.getMessage()); //#else throw new RuntimeException(e.getMessage(),e); //#endif } } public boolean hasNext() { if (isStrRes) return hasNextForStrRes(); if (preparedNextElement != null) return true; UResourceBundle t = null; if (itr.hasNext()) { // Notice, other RuntimeException may be throwed t = itr.next(); } else { return false; } try { preparedNextElement = prepareNext(t); assert_not (preparedNextElement == null, "prepareNext() should not return null"); return true; } catch (DataModuleFormatError e) { // Sadly, we throw RuntimeException also //#if defined(FOUNDATION10) || defined(J2SE13) //## throw new RuntimeException(e.getMessage()); //#else throw new RuntimeException(e.getMessage(),e); //#endif } } public Object next(){ if (hasNext()) { Object t = preparedNextElement; preparedNextElement = null; return t; } else { throw new NoSuchElementException(); } } /** * To prepare data-driven test object for next() call, should not return null */ abstract protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError; } /** * Avoid use Java 1.4 language new assert keyword */ static void assert_is(boolean eq, String msg){ if (!eq) throw new Error("test code itself has error: " + msg); } static void assert_is(boolean eq){ if (!eq) throw new Error("test code itself has error."); } static void assert_not(boolean eq, String msg){ assert_is(!eq, msg); } static void assert_not(boolean eq){ assert_is(!eq); } /** * Internal helper function to get resource with following add-on * * 1. Assert the returned resource is never null. * 2. Check the type of resource. * * The UResourceTypeMismatchException for various get() method is a * RuntimeException which can be silently bypassed. This behavior is a * trouble. One purpose of the class is to enforce format checking for * resource file. We don't want to the exceptions are silently bypassed * and spreaded to our customer's code. * * Notice, the MissingResourceException for get() method is also a * RuntimeException. The caller functions should avoid sepread the execption * silently also. The behavior is modified because some resource are * optional and can be missed. */ static UResourceBundle getFromTable(UResourceBundle res, String key, int expResType) throws DataModuleFormatError{ return getFromTable(res, key, new int[]{expResType}); } static UResourceBundle getFromTable(UResourceBundle res, String key, int[] expResTypes) throws DataModuleFormatError{ assert_is (res != null && key != null && res.getType() == UResourceBundle.TABLE); UResourceBundle t = res.get(key); assert_not (t ==null); int type = t.getType(); Arrays.sort(expResTypes); if (Arrays.binarySearch(expResTypes, type) >= 0) { return t; } else { //#if defined(FOUNDATION10) || defined(J2SE13) //## throw new DataModuleFormatError("Actual type " + t.getType() + " != expected types " + expResTypes + "."); //#else throw new DataModuleFormatError(new UResourceTypeMismatchException("Actual type " + t.getType() + " != expected types " + expResTypes + ".")); //#endif } } /** * Unfortunately, UResourceBundle is unable to treat one string as string array. * This function return a String[] from UResourceBundle, regardless it is an array or a string */ static String[] getStringArrayHelper(UResourceBundle res, String key) throws DataModuleFormatError{ UResourceBundle t = getFromTable(res, key, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING}); return getStringArrayHelper(t); } static String[] getStringArrayHelper(UResourceBundle res) throws DataModuleFormatError{ try{ int type = res.getType(); switch (type) { case UResourceBundle.ARRAY: return res.getStringArray(); case UResourceBundle.STRING: return new String[]{res.getString()}; default: throw new UResourceTypeMismatchException("Only accept ARRAY and STRING types."); } } catch (UResourceTypeMismatchException e){ //#if defined(FOUNDATION10) || defined(J2SE13) //## throw new DataModuleFormatError(e.getMessage()); //#else throw new DataModuleFormatError(e); //#endif } } public static void main(String[] args){ try { TestDataModule m = new ResourceModule("com/ibm/icu/dev/data/testdata/","DataDrivenCollationTest"); System.out.println("hello: " + m.getName()); m.getInfo(); m.getTestDataIterator(); } catch (DataModuleFormatError e) { // TODO Auto-generated catch block System.out.println("???"); e.printStackTrace(); } } private static class UResourceTestData implements TestData{ private UResourceBundle res; private UResourceBundle info; private UResourceBundle settings; private UResourceBundle header; private UResourceBundle data; UResourceTestData(UResourceBundle defaultHeader, UResourceBundle theRes) throws DataModuleFormatError{ assert_is (theRes != null && theRes.getType() == UResourceBundle.TABLE); res = theRes; // unfortunately, actually, data can be either ARRAY or STRING data = getFromTable(res, DATA, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING}); try { // unfortunately, actually, data can be either ARRAY or STRING header = getFromTable(res, HEADER, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING}); } catch (MissingResourceException e){ if (defaultHeader == null) { throw new DataModuleFormatError("Unable to find a header for test data '" + res.getKey() + "' and no default header exist."); } else { header = defaultHeader; } } try{ settings = getFromTable(res, SETTINGS, UResourceBundle.ARRAY); info = getFromTable(res, INFO, UResourceBundle.TABLE); } catch (MissingResourceException e){ // do nothing, left them null; settings = data; } } public String getName() { return res.getKey(); } public DataMap getInfo() { return info == null ? null : new UTableResource(info); } public Iterator getSettingsIterator() { assert_is (settings.getType() == UResourceBundle.ARRAY); return new IteratorAdapter(settings){ protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError { return new UTableResource(nextRes); } }; } public Iterator getDataIterator() { // unfortunately, assert_is (data.getType() == UResourceBundle.ARRAY || data.getType() == UResourceBundle.STRING); return new IteratorAdapter(data){ protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError { return new UArrayResource(header, nextRes); } }; } } private static class UTableResource implements DataMap{ private UResourceBundle res; UTableResource(UResourceBundle theRes){ res = theRes; } public String getString(String key) { String t; try{ t = res.getString(key); } catch (MissingResourceException e){ t = null; } return t; } public Object getObject(String key) { return res.get(key); } } private static class UArrayResource implements DataMap{ private Map theMap; UArrayResource(UResourceBundle theHeader, UResourceBundle theData) throws DataModuleFormatError{ assert_is (theHeader != null && theData != null); String[] header; header = getStringArrayHelper(theHeader); if (theData.getSize() != header.length) throw new DataModuleFormatError("The count of Header and Data is mismatch."); theMap = new HashMap(); for (int i = 0; i < header.length; i++) { if(theData.getType()==UResourceBundle.ARRAY){ theMap.put(header[i], theData.get(i)); }else if(theData.getType()==UResourceBundle.STRING){ theMap.put(header[i], theData.getString()); }else{ throw new DataModuleFormatError("Did not get the expected data!"); } } } public String getString(String key) { Object o = theMap.get(key); UResourceBundle rb; if(o instanceof UResourceBundle) { // unpack ResourceBundle strings rb = (UResourceBundle)o; return rb.getString(); } return (String)o; } public Object getObject(String key) { return theMap.get(key); } } }