3 **********************************************************************
4 * Copyright (c) 2006-2009, International Business Machines
5 * Corporation and others. All Rights Reserved.
6 **********************************************************************
9 package com.ibm.icu.dev.test;
11 import java.util.Arrays;
12 import java.util.HashMap;
13 import java.util.Iterator;
15 import java.util.MissingResourceException;
16 import java.util.NoSuchElementException;
18 import com.ibm.icu.impl.ICUResourceBundle;
19 import com.ibm.icu.util.UResourceBundle;
20 import com.ibm.icu.util.UResourceBundleIterator;
21 import com.ibm.icu.util.UResourceTypeMismatchException;
24 * Represents a collection of test data described in a UResourceBoundle file.
26 * The root of the UResourceBoundle file is a table resource, and it has one
27 * Info and one TestData sub-resources. The Info describes the data module
28 * itself. The TestData, which is a table resource, has a collection of test
31 * The test data is a named table resource which has Info, Settings, Headers,
32 * and Cases sub-resources.
35 * DataModule:table(nofallback){
48 * The test data is expected to be fed to test code by following sequence
50 * for each setting in Setting{
52 * for each test data in Cases{
57 * For detail of the specification, please refer to the code. The code is
58 * initially ported from "icu4c/source/tools/ctestfw/unicode/tstdtmod.h"
59 * and should be maintained parallelly.
61 * @author Raymond Yang
63 class ResourceModule implements TestDataModule {
64 private static final String INFO = "Info";
65 // private static final String DESCRIPTION = "Description";
66 // private static final String LONG_DESCRIPTION = "LongDescription";
67 private static final String TEST_DATA = "TestData";
68 private static final String SETTINGS = "Settings";
69 private static final String HEADER = "Headers";
70 private static final String DATA = "Cases";
75 UResourceBundle defaultHeader;
76 UResourceBundle testData;
78 ResourceModule(String baseName, String localeName) throws DataModuleFormatError{
80 res = (UResourceBundle) UResourceBundle.getBundleInstance(baseName, localeName);
81 info = getFromTable(res, INFO, UResourceBundle.TABLE);
82 testData = getFromTable(res, TEST_DATA, UResourceBundle.TABLE);
85 // unfortunately, actually, data can be either ARRAY or STRING
86 defaultHeader = getFromTable(info, HEADER, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
87 } catch (MissingResourceException e){
92 public String getName() {
96 public DataMap getInfo() {
97 return new UTableResource(info);
100 public TestData getTestData(String testName) throws DataModuleFormatError {
101 return new UResourceTestData(defaultHeader, testData.get(testName));
104 public Iterator getTestDataIterator() {
105 return new IteratorAdapter(testData){
106 protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError {
107 return new UResourceTestData(defaultHeader, nextRes);
113 * To make UResourceBundleIterator works like Iterator
114 * and return various data-driven test object for next() call
116 * @author Raymond Yang
118 private abstract static class IteratorAdapter implements Iterator{
119 private UResourceBundle res;
120 private UResourceBundleIterator itr;
121 private Object preparedNextElement = null;
122 // fix a strange behavior for UResourceBundleIterator for
123 // UResourceBundle.STRING. It support hasNext(), but does
124 // not support next() now.
126 // Use the iterated resource itself as the result from next() call
127 private boolean isStrRes = false;
128 private boolean isStrResPrepared = false; // for STRING resouce, we only prepare once
130 IteratorAdapter(UResourceBundle theRes) {
131 assert_not (theRes == null);
133 itr = ((ICUResourceBundle)res).getIterator();
134 isStrRes = res.getType() == UResourceBundle.STRING;
137 public void remove() {
141 private boolean hasNextForStrRes(){
142 assert_is (isStrRes);
143 assert_not (!isStrResPrepared && preparedNextElement != null);
144 if (isStrResPrepared && preparedNextElement != null) return true;
145 if (isStrResPrepared && preparedNextElement == null) return false; // only prepare once
146 assert_is (!isStrResPrepared && preparedNextElement == null);
149 preparedNextElement = prepareNext(res);
150 assert_not (preparedNextElement == null, "prepareNext() should not return null");
151 isStrResPrepared = true; // toggle the tag
153 } catch (DataModuleFormatError e) {
154 //#if defined(FOUNDATION10) || defined(J2SE13)
155 //## throw new RuntimeException(e.getMessage());
157 throw new RuntimeException(e.getMessage(),e);
161 public boolean hasNext() {
162 if (isStrRes) return hasNextForStrRes();
164 if (preparedNextElement != null) return true;
165 UResourceBundle t = null;
167 // Notice, other RuntimeException may be throwed
174 preparedNextElement = prepareNext(t);
175 assert_not (preparedNextElement == null, "prepareNext() should not return null");
177 } catch (DataModuleFormatError e) {
178 // Sadly, we throw RuntimeException also
179 //#if defined(FOUNDATION10) || defined(J2SE13)
180 //## throw new RuntimeException(e.getMessage());
182 throw new RuntimeException(e.getMessage(),e);
187 public Object next(){
189 Object t = preparedNextElement;
190 preparedNextElement = null;
193 throw new NoSuchElementException();
197 * To prepare data-driven test object for next() call, should not return null
199 abstract protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError;
204 * Avoid use Java 1.4 language new assert keyword
206 static void assert_is(boolean eq, String msg){
207 if (!eq) throw new Error("test code itself has error: " + msg);
209 static void assert_is(boolean eq){
210 if (!eq) throw new Error("test code itself has error.");
212 static void assert_not(boolean eq, String msg){
215 static void assert_not(boolean eq){
220 * Internal helper function to get resource with following add-on
222 * 1. Assert the returned resource is never null.
223 * 2. Check the type of resource.
225 * The UResourceTypeMismatchException for various get() method is a
226 * RuntimeException which can be silently bypassed. This behavior is a
227 * trouble. One purpose of the class is to enforce format checking for
228 * resource file. We don't want to the exceptions are silently bypassed
229 * and spreaded to our customer's code.
231 * Notice, the MissingResourceException for get() method is also a
232 * RuntimeException. The caller functions should avoid sepread the execption
233 * silently also. The behavior is modified because some resource are
234 * optional and can be missed.
236 static UResourceBundle getFromTable(UResourceBundle res, String key, int expResType) throws DataModuleFormatError{
237 return getFromTable(res, key, new int[]{expResType});
240 static UResourceBundle getFromTable(UResourceBundle res, String key, int[] expResTypes) throws DataModuleFormatError{
241 assert_is (res != null && key != null && res.getType() == UResourceBundle.TABLE);
242 UResourceBundle t = res.get(key);
244 assert_not (t ==null);
245 int type = t.getType();
246 Arrays.sort(expResTypes);
247 if (Arrays.binarySearch(expResTypes, type) >= 0) {
250 //#if defined(FOUNDATION10) || defined(J2SE13)
251 //## throw new DataModuleFormatError("Actual type " + t.getType() + " != expected types " + expResTypes + ".");
253 throw new DataModuleFormatError(new UResourceTypeMismatchException("Actual type " + t.getType() + " != expected types " + expResTypes + "."));
259 * Unfortunately, UResourceBundle is unable to treat one string as string array.
260 * This function return a String[] from UResourceBundle, regardless it is an array or a string
262 static String[] getStringArrayHelper(UResourceBundle res, String key) throws DataModuleFormatError{
263 UResourceBundle t = getFromTable(res, key, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
264 return getStringArrayHelper(t);
267 static String[] getStringArrayHelper(UResourceBundle res) throws DataModuleFormatError{
269 int type = res.getType();
271 case UResourceBundle.ARRAY:
272 return res.getStringArray();
273 case UResourceBundle.STRING:
274 return new String[]{res.getString()};
276 throw new UResourceTypeMismatchException("Only accept ARRAY and STRING types.");
278 } catch (UResourceTypeMismatchException e){
279 //#if defined(FOUNDATION10) || defined(J2SE13)
280 //## throw new DataModuleFormatError(e.getMessage());
282 throw new DataModuleFormatError(e);
287 public static void main(String[] args){
289 TestDataModule m = new ResourceModule("com/ibm/icu/dev/data/testdata/","DataDrivenCollationTest");
290 System.out.println("hello: " + m.getName());
292 m.getTestDataIterator();
293 } catch (DataModuleFormatError e) {
294 // TODO Auto-generated catch block
295 System.out.println("???");
300 private static class UResourceTestData implements TestData{
301 private UResourceBundle res;
302 private UResourceBundle info;
303 private UResourceBundle settings;
304 private UResourceBundle header;
305 private UResourceBundle data;
307 UResourceTestData(UResourceBundle defaultHeader, UResourceBundle theRes) throws DataModuleFormatError{
309 assert_is (theRes != null && theRes.getType() == UResourceBundle.TABLE);
311 // unfortunately, actually, data can be either ARRAY or STRING
312 data = getFromTable(res, DATA, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
317 // unfortunately, actually, data can be either ARRAY or STRING
318 header = getFromTable(res, HEADER, new int[]{UResourceBundle.ARRAY, UResourceBundle.STRING});
319 } catch (MissingResourceException e){
320 if (defaultHeader == null) {
321 throw new DataModuleFormatError("Unable to find a header for test data '" + res.getKey() + "' and no default header exist.");
323 header = defaultHeader;
327 settings = getFromTable(res, SETTINGS, UResourceBundle.ARRAY);
328 info = getFromTable(res, INFO, UResourceBundle.TABLE);
329 } catch (MissingResourceException e){
330 // do nothing, left them null;
335 public String getName() {
339 public DataMap getInfo() {
340 return info == null ? null : new UTableResource(info);
343 public Iterator getSettingsIterator() {
344 assert_is (settings.getType() == UResourceBundle.ARRAY);
345 return new IteratorAdapter(settings){
346 protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError {
347 return new UTableResource(nextRes);
352 public Iterator getDataIterator() {
354 assert_is (data.getType() == UResourceBundle.ARRAY
355 || data.getType() == UResourceBundle.STRING);
356 return new IteratorAdapter(data){
357 protected Object prepareNext(UResourceBundle nextRes) throws DataModuleFormatError {
358 return new UArrayResource(header, nextRes);
364 private static class UTableResource implements DataMap{
365 private UResourceBundle res;
367 UTableResource(UResourceBundle theRes){
370 public String getString(String key) {
373 t = res.getString(key);
374 } catch (MissingResourceException e){
379 public Object getObject(String key) {
385 private static class UArrayResource implements DataMap{
387 UArrayResource(UResourceBundle theHeader, UResourceBundle theData) throws DataModuleFormatError{
388 assert_is (theHeader != null && theData != null);
391 header = getStringArrayHelper(theHeader);
392 if (theData.getSize() != header.length)
393 throw new DataModuleFormatError("The count of Header and Data is mismatch.");
394 theMap = new HashMap();
395 for (int i = 0; i < header.length; i++) {
396 if(theData.getType()==UResourceBundle.ARRAY){
397 theMap.put(header[i], theData.get(i));
398 }else if(theData.getType()==UResourceBundle.STRING){
399 theMap.put(header[i], theData.getString());
401 throw new DataModuleFormatError("Did not get the expected data!");
407 public String getString(String key) {
408 Object o = theMap.get(key);
410 if(o instanceof UResourceBundle) {
411 // unpack ResourceBundle strings
412 rb = (UResourceBundle)o;
413 return rb.getString();
417 public Object getObject(String key) {
418 return theMap.get(key);