001: //##header
002: /*
003: **********************************************************************
004: * Copyright (c) 2006, International Business Machines
005: * Corporation and others. All Rights Reserved.
006: **********************************************************************
007: * Created on 2006-4-21
008: */
009: package com.ibm.icu.dev.test;
010:
011: import java.util.Arrays;
012: import java.util.Enumeration;
013: import java.util.HashMap;
014: import java.util.Iterator;
015: import java.util.Map;
016: import java.util.MissingResourceException;
017: import java.util.NoSuchElementException;
018:
019: import com.ibm.icu.impl.ICUResourceBundle;
020: import com.ibm.icu.impl.ICUResourceBundleIterator;
021: import com.ibm.icu.util.UResourceBundle;
022: import com.ibm.icu.util.UResourceTypeMismatchException;
023:
024: /**
025: * Represents a collection of test data described in a UResourceBoundle file.
026: *
027: * The root of the UResourceBoundle file is a table resource, and it has one
028: * Info and one TestData sub-resources. The Info describes the data module
029: * itself. The TestData, which is a table resource, has a collection of test
030: * data.
031: *
032: * The test data is a named table resource which has Info, Settings, Headers,
033: * and Cases sub-resources.
034: *
035: * <pre>
036: * DataModule:table(nofallback){
037: * Info:table {}
038: * TestData:table {
039: * entry_name:table{
040: * Info:table{}
041: * Settings:array{}
042: * Headers:array{}
043: * Cases:array{}
044: * }
045: * }
046: * }
047: * </pre>
048: *
049: * The test data is expected to be fed to test code by following sequence
050: *
051: * for each setting in Setting{
052: * prepare the setting
053: * for each test data in Cases{
054: * perform the test
055: * }
056: * }
057: *
058: * For detail of the specification, please refer to the code. The code is
059: * initially ported from "icu4c/source/tools/ctestfw/unicode/tstdtmod.h"
060: * and should be maintained parallelly.
061: *
062: * @author Raymond Yang
063: */
064: class ResourceModule implements TestDataModule {
065: private static final String INFO = "Info";
066: // private static final String DESCRIPTION = "Description";
067: // private static final String LONG_DESCRIPTION = "LongDescription";
068: private static final String TEST_DATA = "TestData";
069: private static final String SETTINGS = "Settings";
070: private static final String HEADER = "Headers";
071: private static final String DATA = "Cases";
072:
073: ICUResourceBundle res;
074: ICUResourceBundle info;
075: ICUResourceBundle defaultHeader;
076: ICUResourceBundle testData;
077:
078: ResourceModule(String baseName, String localeName)
079: throws DataModuleFormatError {
080:
081: res = (ICUResourceBundle) UResourceBundle.getBundleInstance(
082: baseName, localeName);
083: info = getFromTable(res, INFO, ICUResourceBundle.TABLE);
084: testData = getFromTable(res, TEST_DATA, ICUResourceBundle.TABLE);
085:
086: try {
087: // unfortunately, actually, data can be either ARRAY or STRING
088: defaultHeader = getFromTable(info, HEADER, new int[] {
089: ICUResourceBundle.ARRAY, ICUResourceBundle.STRING });
090: } catch (MissingResourceException e) {
091: defaultHeader = null;
092: }
093: }
094:
095: public String getName() {
096: return res.getKey();
097: }
098:
099: public DataMap getInfo() {
100: return new UTableResource(info);
101: }
102:
103: public TestData getTestData(String testName)
104: throws DataModuleFormatError {
105: return new UResourceTestData(defaultHeader, testData
106: .get(testName));
107: }
108:
109: public Iterator getTestDataIterator() {
110: return new IteratorAdapter(testData) {
111: protected Object prepareNext(ICUResourceBundle nextRes)
112: throws DataModuleFormatError {
113: return new UResourceTestData(defaultHeader, nextRes);
114: }
115: };
116: }
117:
118: /**
119: * To make ICUResourceBundleIterator works like Iterator
120: * and return various data-driven test object for next() call
121: *
122: * @author Raymond Yang
123: */
124: private abstract static class IteratorAdapter implements Iterator {
125: private ICUResourceBundle res;
126: private ICUResourceBundleIterator itr;
127: private Object preparedNextElement = null;
128: // fix a strange behavior for ICUResourceBundleIterator for
129: // ICUResourceBundle.STRING. It support hasNext(), but does
130: // not support next() now.
131: //
132: // Use the iterated resource itself as the result from next() call
133: private boolean isStrRes = false;
134: private boolean isStrResPrepared = false; // for STRING resouce, we only prepare once
135:
136: IteratorAdapter(ICUResourceBundle theRes) {
137: assert_not(theRes == null);
138: res = theRes;
139: itr = res.getIterator();
140: isStrRes = res.getType() == ICUResourceBundle.STRING;
141: }
142:
143: public void remove() {
144: // do nothing
145: }
146:
147: private boolean hasNextForStrRes() {
148: assert_is(isStrRes);
149: assert_not(!isStrResPrepared && preparedNextElement != null);
150: if (isStrResPrepared && preparedNextElement != null)
151: return true;
152: if (isStrResPrepared && preparedNextElement == null)
153: return false; // only prepare once
154: assert_is(!isStrResPrepared && preparedNextElement == null);
155:
156: try {
157: preparedNextElement = prepareNext(res);
158: assert_not(preparedNextElement == null,
159: "prepareNext() should not return null");
160: isStrResPrepared = true; // toggle the tag
161: return true;
162: } catch (DataModuleFormatError e) {
163: //#ifdef FOUNDATION
164: //## throw new RuntimeException(e.getMessage());
165: //#else
166: throw new RuntimeException(e.getMessage(), e);
167: //#endif
168: }
169: }
170:
171: public boolean hasNext() {
172: if (isStrRes)
173: return hasNextForStrRes();
174:
175: if (preparedNextElement != null)
176: return true;
177: ICUResourceBundle t = null;
178: if (itr.hasNext()) {
179: // Notice, other RuntimeException may be throwed
180: t = itr.next();
181: } else {
182: return false;
183: }
184:
185: try {
186: preparedNextElement = prepareNext(t);
187: assert_not(preparedNextElement == null,
188: "prepareNext() should not return null");
189: return true;
190: } catch (DataModuleFormatError e) {
191: // Sadly, we throw RuntimeException also
192: //#ifdef FOUNDATION
193: //## throw new RuntimeException(e.getMessage());
194: //#else
195: throw new RuntimeException(e.getMessage(), e);
196: //#endif
197: }
198: }
199:
200: public Object next() {
201: if (hasNext()) {
202: Object t = preparedNextElement;
203: preparedNextElement = null;
204: return t;
205: } else {
206: throw new NoSuchElementException();
207: }
208: }
209:
210: /**
211: * To prepare data-driven test object for next() call, should not return null
212: */
213: abstract protected Object prepareNext(ICUResourceBundle nextRes)
214: throws DataModuleFormatError;
215: }
216:
217: /**
218: * Avoid use Java 1.4 language new assert keyword
219: */
220: static void assert_is(boolean eq, String msg) {
221: if (!eq)
222: throw new Error("test code itself has error: " + msg);
223: }
224:
225: static void assert_is(boolean eq) {
226: if (!eq)
227: throw new Error("test code itself has error.");
228: }
229:
230: static void assert_not(boolean eq, String msg) {
231: assert_is(!eq, msg);
232: }
233:
234: static void assert_not(boolean eq) {
235: assert_is(!eq);
236: }
237:
238: /**
239: * Internal helper function to get resource with following add-on
240: *
241: * 1. Assert the returned resource is never null.
242: * 2. Check the type of resource.
243: *
244: * The UResourceTypeMismatchException for various get() method is a
245: * RuntimeException which can be silently bypassed. This behavior is a
246: * trouble. One purpose of the class is to enforce format checking for
247: * resource file. We don't want to the exceptions are silently bypassed
248: * and spreaded to our customer's code.
249: *
250: * Notice, the MissingResourceException for get() method is also a
251: * RuntimeException. The caller functions should avoid sepread the execption
252: * silently also. The behavior is modified because some resource are
253: * optional and can be missed.
254: */
255: static ICUResourceBundle getFromTable(ICUResourceBundle res,
256: String key, int expResType) throws DataModuleFormatError {
257: return getFromTable(res, key, new int[] { expResType });
258: }
259:
260: static ICUResourceBundle getFromTable(ICUResourceBundle res,
261: String key, int[] expResTypes) throws DataModuleFormatError {
262: assert_is(res != null && key != null
263: && res.getType() == ICUResourceBundle.TABLE);
264: ICUResourceBundle t = res.get(key);
265:
266: assert_not(t == null);
267: int type = t.getType();
268: Arrays.sort(expResTypes);
269: if (Arrays.binarySearch(expResTypes, type) >= 0) {
270: return t;
271: } else {
272: //#ifdef FOUNDATION
273: //## throw new DataModuleFormatError("Actual type " + t.getType() + " != expected types " + expResTypes + ".");
274: //#else
275: throw new DataModuleFormatError(
276: new UResourceTypeMismatchException("Actual type "
277: + t.getType() + " != expected types "
278: + expResTypes + "."));
279: //#endif
280: }
281: }
282:
283: /**
284: * Unfortunately, ICUResourceBundle is unable to treat one string as string array.
285: * This function return a String[] from ICUResourceBundle, regardless it is an array or a string
286: */
287: static String[] getStringArrayHelper(ICUResourceBundle res,
288: String key) throws DataModuleFormatError {
289: ICUResourceBundle t = getFromTable(res, key, new int[] {
290: ICUResourceBundle.ARRAY, ICUResourceBundle.STRING });
291: return getStringArrayHelper(t);
292: }
293:
294: static String[] getStringArrayHelper(ICUResourceBundle res)
295: throws DataModuleFormatError {
296: try {
297: int type = res.getType();
298: switch (type) {
299: case ICUResourceBundle.ARRAY:
300: return res.getStringArray();
301: case ICUResourceBundle.STRING:
302: return new String[] { res.getString() };
303: default:
304: throw new UResourceTypeMismatchException(
305: "Only accept ARRAY and STRING types.");
306: }
307: } catch (UResourceTypeMismatchException e) {
308: //#ifdef FOUNDATION
309: //## throw new DataModuleFormatError(e.getMessage());
310: //#else
311: throw new DataModuleFormatError(e);
312: //#endif
313: }
314: }
315:
316: public static void main(String[] args) {
317: try {
318: TestDataModule m = new ResourceModule(
319: "com/ibm/icu/dev/data/testdata/",
320: "DataDrivenCollationTest");
321: System.out.println("hello: " + m.getName());
322: m.getInfo();
323: m.getTestDataIterator();
324: } catch (DataModuleFormatError e) {
325: // TODO Auto-generated catch block
326: System.out.println("???");
327: e.printStackTrace();
328: }
329: }
330:
331: private static class UResourceTestData implements TestData {
332: private ICUResourceBundle res;
333: private ICUResourceBundle info;
334: private ICUResourceBundle settings;
335: private ICUResourceBundle header;
336: private ICUResourceBundle data;
337:
338: UResourceTestData(ICUResourceBundle defaultHeader,
339: ICUResourceBundle theRes) throws DataModuleFormatError {
340:
341: assert_is(theRes != null
342: && theRes.getType() == ICUResourceBundle.TABLE);
343: res = theRes;
344: // unfortunately, actually, data can be either ARRAY or STRING
345: data = getFromTable(res, DATA, new int[] {
346: ICUResourceBundle.ARRAY, ICUResourceBundle.STRING });
347:
348: try {
349: // unfortunately, actually, data can be either ARRAY or STRING
350: header = getFromTable(res, HEADER, new int[] {
351: ICUResourceBundle.ARRAY,
352: ICUResourceBundle.STRING });
353: } catch (MissingResourceException e) {
354: if (defaultHeader == null) {
355: throw new DataModuleFormatError(
356: "Unable to find a header for test data '"
357: + res.getKey()
358: + "' and no default header exist.");
359: } else {
360: header = defaultHeader;
361: }
362: }
363: try {
364: settings = getFromTable(res, SETTINGS,
365: ICUResourceBundle.ARRAY);
366: info = getFromTable(res, INFO, ICUResourceBundle.TABLE);
367: } catch (MissingResourceException e) {
368: // do nothing, left them null;
369: settings = data;
370: }
371: }
372:
373: public String getName() {
374: return res.getKey();
375: }
376:
377: public DataMap getInfo() {
378: return info == null ? null : new UTableResource(info);
379: }
380:
381: public Iterator getSettingsIterator() {
382: assert_is(settings.getType() == ICUResourceBundle.ARRAY);
383: return new IteratorAdapter(settings) {
384: protected Object prepareNext(ICUResourceBundle nextRes)
385: throws DataModuleFormatError {
386: return new UTableResource(nextRes);
387: }
388: };
389: }
390:
391: public Iterator getDataIterator() {
392: // unfortunately,
393: assert_is(data.getType() == ICUResourceBundle.ARRAY
394: || data.getType() == ICUResourceBundle.STRING);
395: return new IteratorAdapter(data) {
396: protected Object prepareNext(ICUResourceBundle nextRes)
397: throws DataModuleFormatError {
398: return new UArrayResource(header, nextRes);
399: }
400: };
401: }
402: }
403:
404: private static class UTableResource implements DataMap {
405: private ICUResourceBundle res;
406:
407: UTableResource(ICUResourceBundle theRes) {
408: res = theRes;
409: }
410:
411: public String getString(String key) {
412: String t;
413: try {
414: t = res.getString(key);
415: } catch (MissingResourceException e) {
416: t = null;
417: }
418: return t;
419: }
420:
421: public Object getObject(String key) {
422:
423: return res.get(key);
424: }
425: }
426:
427: private static class UArrayResource implements DataMap {
428: private Map theMap;
429:
430: UArrayResource(ICUResourceBundle theHeader,
431: ICUResourceBundle theData) throws DataModuleFormatError {
432: assert_is(theHeader != null && theData != null);
433: String[] header;
434:
435: header = getStringArrayHelper(theHeader);
436: if (theData.getSize() != header.length)
437: throw new DataModuleFormatError(
438: "The count of Header and Data is mismatch.");
439: theMap = new HashMap();
440: for (int i = 0; i < header.length; i++) {
441: if (theData.getType() == ICUResourceBundle.ARRAY) {
442: theMap.put(header[i], theData.get(i));
443: } else if (theData.getType() == ICUResourceBundle.STRING) {
444: theMap.put(header[i], theData.getString());
445: } else {
446: throw new DataModuleFormatError(
447: "Did not get the expected data!");
448: }
449: }
450:
451: }
452:
453: public String getString(String key) {
454: return (String) theMap.get(key);
455: }
456:
457: public Object getObject(String key) {
458: return theMap.get(key);
459: }
460: }
461: }
|