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