• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2016 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 /*
5  *******************************************************************************
6  * Copyright (C) 2009-2016, International Business Machines Corporation and
7  * others. All Rights Reserved.
8  *******************************************************************************
9  */
10 package ohos.global.icu.impl;
11 
12 import java.lang.ref.SoftReference;
13 import java.util.HashMap;
14 import java.util.Map;
15 import java.util.MissingResourceException;
16 
17 import ohos.global.icu.impl.CurrencyData.CurrencyDisplayInfo;
18 import ohos.global.icu.impl.CurrencyData.CurrencyDisplayInfoProvider;
19 import ohos.global.icu.impl.CurrencyData.CurrencyFormatInfo;
20 import ohos.global.icu.impl.CurrencyData.CurrencySpacingInfo;
21 import ohos.global.icu.impl.ICUResourceBundle.OpenType;
22 import ohos.global.icu.util.ICUException;
23 import ohos.global.icu.util.ULocale;
24 import ohos.global.icu.util.UResourceBundle;
25 
26 /**
27  * @hide exposed on OHOS
28  */
29 public class ICUCurrencyDisplayInfoProvider implements CurrencyDisplayInfoProvider {
ICUCurrencyDisplayInfoProvider()30     public ICUCurrencyDisplayInfoProvider() {
31     }
32 
33     /**
34      * Single-item cache for ICUCurrencyDisplayInfo keyed by locale.
35      */
36     private volatile ICUCurrencyDisplayInfo currencyDisplayInfoCache = null;
37 
38     @Override
getInstance(ULocale locale, boolean withFallback)39     public CurrencyDisplayInfo getInstance(ULocale locale, boolean withFallback) {
40         // Make sure the locale is non-null (this can happen during deserialization):
41         if (locale == null) { locale = ULocale.ROOT; }
42         ICUCurrencyDisplayInfo instance = currencyDisplayInfoCache;
43         if (instance == null || !instance.locale.equals(locale) || instance.fallback != withFallback) {
44             ICUResourceBundle rb;
45             if (withFallback) {
46                 rb = ICUResourceBundle.getBundleInstance(
47                         ICUData.ICU_CURR_BASE_NAME, locale, OpenType.LOCALE_DEFAULT_ROOT);
48             } else {
49                 try {
50                     rb = ICUResourceBundle.getBundleInstance(
51                             ICUData.ICU_CURR_BASE_NAME, locale, OpenType.LOCALE_ONLY);
52                 } catch (MissingResourceException e) {
53                     return null;
54                 }
55             }
56             instance = new ICUCurrencyDisplayInfo(locale, rb, withFallback);
57             currencyDisplayInfoCache = instance;
58         }
59         return instance;
60     }
61 
62     @Override
hasData()63     public boolean hasData() {
64         return true;
65     }
66 
67     /**
68      * This class performs data loading for currencies and keeps data in lightweight cache.
69      */
70     static class ICUCurrencyDisplayInfo extends CurrencyDisplayInfo {
71         final ULocale locale;
72         final boolean fallback;
73         private final ICUResourceBundle rb;
74 
75         /**
76          * Single-item cache for getName(), getSymbol(), and getFormatInfo().
77          * Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
78          */
79         private volatile FormattingData formattingDataCache = null;
80 
81         /**
82          * Single-item cache for variant symbols.
83          * Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
84          */
85         private volatile VariantSymbol variantSymbolCache = null;
86 
87         /**
88          * Single-item cache for getPluralName().
89          *
90          * <p>
91          * array[0] is the ISO code.<br>
92          * array[1+p] is the plural name where p=standardPlural.ordinal().
93          *
94          * <p>
95          * Holds data for only one currency. If another currency is requested, the old cache item is overwritten.
96          */
97         private volatile String[] pluralsDataCache = null;
98 
99         /**
100          * Cache for symbolMap() and nameMap().
101          */
102         private volatile SoftReference<ParsingData> parsingDataCache = new SoftReference<>(null);
103 
104         /**
105          * Cache for getUnitPatterns().
106          */
107         private volatile Map<String, String> unitPatternsCache = null;
108 
109         /**
110          * Cache for getSpacingInfo().
111          */
112         private volatile CurrencySpacingInfo spacingInfoCache = null;
113 
114         static class FormattingData {
115             final String isoCode;
116             String displayName = null;
117             String symbol = null;
118             CurrencyFormatInfo formatInfo = null;
119 
FormattingData(String isoCode)120             FormattingData(String isoCode) { this.isoCode = isoCode; }
121         }
122 
123         static class VariantSymbol {
124             final String isoCode;
125             final String variant;
126             String symbol = null;
127 
VariantSymbol(String isoCode, String variant)128             VariantSymbol(String isoCode, String variant) {
129                 this.isoCode = isoCode;
130                 this.variant = variant;
131             }
132         }
133 
134         static class ParsingData {
135             Map<String, String> symbolToIsoCode = new HashMap<>();
136             Map<String, String> nameToIsoCode = new HashMap<>();
137         }
138 
139         ////////////////////////
140         /// START PUBLIC API ///
141         ////////////////////////
142 
ICUCurrencyDisplayInfo(ULocale locale, ICUResourceBundle rb, boolean fallback)143         public ICUCurrencyDisplayInfo(ULocale locale, ICUResourceBundle rb, boolean fallback) {
144             this.locale = locale;
145             this.fallback = fallback;
146             this.rb = rb;
147         }
148 
149         @Override
getULocale()150         public ULocale getULocale() {
151             return rb.getULocale();
152         }
153 
154         @Override
getName(String isoCode)155         public String getName(String isoCode) {
156             FormattingData formattingData = fetchFormattingData(isoCode);
157 
158             // Fall back to ISO Code
159             if (formattingData.displayName == null && fallback) {
160                 return isoCode;
161             }
162             return formattingData.displayName;
163         }
164 
165         @Override
getSymbol(String isoCode)166         public String getSymbol(String isoCode) {
167             FormattingData formattingData = fetchFormattingData(isoCode);
168 
169             // Fall back to ISO Code
170             if (formattingData.symbol == null && fallback) {
171                 return isoCode;
172             }
173             return formattingData.symbol;
174         }
175 
176         @Override
getNarrowSymbol(String isoCode)177         public String getNarrowSymbol(String isoCode) {
178             VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "narrow");
179 
180             // Fall back to regular symbol
181             if (variantSymbol.symbol == null && fallback) {
182                 return getSymbol(isoCode);
183             }
184             return variantSymbol.symbol;
185         }
186 
187         @Override
getFormalSymbol(String isoCode)188         public String getFormalSymbol(String isoCode) {
189             VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "formal");
190 
191             // Fall back to regular symbol
192             if (variantSymbol.symbol == null && fallback) {
193                 return getSymbol(isoCode);
194             }
195             return variantSymbol.symbol;
196         }
197 
198         @Override
getVariantSymbol(String isoCode)199         public String getVariantSymbol(String isoCode) {
200             VariantSymbol variantSymbol = fetchVariantSymbol(isoCode, "variant");
201 
202             // Fall back to regular symbol
203             if (variantSymbol.symbol == null && fallback) {
204                 return getSymbol(isoCode);
205             }
206             return variantSymbol.symbol;
207         }
208 
209         @Override
getPluralName(String isoCode, String pluralKey )210         public String getPluralName(String isoCode, String pluralKey ) {
211             StandardPlural plural = StandardPlural.orNullFromString(pluralKey);
212             String[] pluralsData = fetchPluralsData(isoCode);
213 
214             // See http://unicode.org/reports/tr35/#Currencies, especially the fallback rule.
215             String result = null;
216             if (plural != null) {
217                 result = pluralsData[1 + plural.ordinal()];
218             }
219             if (result == null && fallback) {
220                 // First fall back to the "other" plural variant
221                 // Note: If plural is already "other", this fallback is benign
222                 result = pluralsData[1 + StandardPlural.OTHER.ordinal()];
223             }
224             if (result == null && fallback) {
225                 // If that fails, fall back to the display name
226                 FormattingData formattingData = fetchFormattingData(isoCode);
227                 result = formattingData.displayName;
228             }
229             if (result == null && fallback) {
230                 // If all else fails, return the ISO code
231                 result = isoCode;
232             }
233             return result;
234         }
235 
236         @Override
symbolMap()237         public Map<String, String> symbolMap() {
238             ParsingData parsingData = fetchParsingData();
239             return parsingData.symbolToIsoCode;
240         }
241 
242         @Override
nameMap()243         public Map<String, String> nameMap() {
244             ParsingData parsingData = fetchParsingData();
245             return parsingData.nameToIsoCode;
246         }
247 
248         @Override
getUnitPatterns()249         public Map<String, String> getUnitPatterns() {
250             // Default result is the empty map. Callers who require a pattern will have to
251             // supply a default.
252             Map<String,String> unitPatterns = fetchUnitPatterns();
253             return unitPatterns;
254         }
255 
256         @Override
getFormatInfo(String isoCode)257         public CurrencyFormatInfo getFormatInfo(String isoCode) {
258             FormattingData formattingData = fetchFormattingData(isoCode);
259             return formattingData.formatInfo;
260         }
261 
262         @Override
getSpacingInfo()263         public CurrencySpacingInfo getSpacingInfo() {
264             CurrencySpacingInfo spacingInfo = fetchSpacingInfo();
265 
266             // Fall back to DEFAULT
267             if ((!spacingInfo.hasBeforeCurrency || !spacingInfo.hasAfterCurrency) && fallback) {
268                 return CurrencySpacingInfo.DEFAULT;
269             }
270             return spacingInfo;
271         }
272 
273         /////////////////////////////////////////////
274         /// END PUBLIC API -- START DATA FRONTEND ///
275         /////////////////////////////////////////////
276 
fetchFormattingData(String isoCode)277         FormattingData fetchFormattingData(String isoCode) {
278             FormattingData result = formattingDataCache;
279             if (result == null || !result.isoCode.equals(isoCode)) {
280                 result = new FormattingData(isoCode);
281                 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCIES);
282                 sink.formattingData = result;
283                 rb.getAllItemsWithFallbackNoFail("Currencies/" + isoCode, sink);
284                 formattingDataCache = result;
285             }
286             return result;
287         }
288 
fetchVariantSymbol(String isoCode, String variant)289         VariantSymbol fetchVariantSymbol(String isoCode, String variant) {
290             VariantSymbol result = variantSymbolCache;
291             if (result == null || !result.isoCode.equals(isoCode) || !result.variant.equals(variant)) {
292                 result = new VariantSymbol(isoCode, variant);
293                 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_VARIANT);
294                 sink.variantSymbol = result;
295                 rb.getAllItemsWithFallbackNoFail("Currencies%" + variant + "/" + isoCode, sink);
296                 variantSymbolCache = result;
297             }
298             return result;
299         }
300 
fetchPluralsData(String isoCode)301         String[] fetchPluralsData(String isoCode) {
302             String[] result = pluralsDataCache;
303             if (result == null || !result[0].equals(isoCode)) {
304                 result = new String[1 + StandardPlural.COUNT];
305                 result[0] = isoCode;
306                 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_PLURALS);
307                 sink.pluralsData = result;
308                 rb.getAllItemsWithFallbackNoFail("CurrencyPlurals/" + isoCode, sink);
309                 pluralsDataCache = result;
310             }
311             return result;
312         }
313 
fetchParsingData()314         ParsingData fetchParsingData() {
315             ParsingData result = parsingDataCache.get();
316             if (result == null) {
317                 result = new ParsingData();
318                 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.TOP);
319                 sink.parsingData = result;
320                 rb.getAllItemsWithFallback("", sink);
321                 parsingDataCache = new SoftReference<>(result);
322             }
323             return result;
324         }
325 
fetchUnitPatterns()326         Map<String, String> fetchUnitPatterns() {
327             Map<String, String> result = unitPatternsCache;
328             if (result == null) {
329                 result = new HashMap<>();
330                 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_UNIT_PATTERNS);
331                 sink.unitPatterns = result;
332                 rb.getAllItemsWithFallback("CurrencyUnitPatterns", sink);
333                 unitPatternsCache = result;
334             }
335             return result;
336         }
337 
fetchSpacingInfo()338         CurrencySpacingInfo fetchSpacingInfo() {
339             CurrencySpacingInfo result = spacingInfoCache;
340             if (result == null) {
341                 result = new CurrencySpacingInfo();
342                 CurrencySink sink = new CurrencySink(!fallback, CurrencySink.EntrypointTable.CURRENCY_SPACING);
343                 sink.spacingInfo = result;
344                 rb.getAllItemsWithFallback("currencySpacing", sink);
345                 spacingInfoCache = result;
346             }
347             return result;
348         }
349 
350         ////////////////////////////////////////////
351         /// END DATA FRONTEND -- START DATA SINK ///
352         ////////////////////////////////////////////
353 
354         private static final class CurrencySink extends UResource.Sink {
355             final boolean noRoot;
356             final EntrypointTable entrypointTable;
357 
358             // The fields to be populated on this run of the data sink will be non-null.
359             FormattingData formattingData = null;
360             String[] pluralsData = null;
361             ParsingData parsingData = null;
362             Map<String, String> unitPatterns = null;
363             CurrencySpacingInfo spacingInfo = null;
364             VariantSymbol variantSymbol = null;
365 
366             enum EntrypointTable {
367                 // For Parsing:
368                 TOP,
369 
370                 // For Formatting:
371                 CURRENCIES,
372                 CURRENCY_PLURALS,
373                 CURRENCY_VARIANT,
374                 CURRENCY_SPACING,
375                 CURRENCY_UNIT_PATTERNS
376             }
377 
CurrencySink(boolean noRoot, EntrypointTable entrypointTable)378             CurrencySink(boolean noRoot, EntrypointTable entrypointTable) {
379                 this.noRoot = noRoot;
380                 this.entrypointTable = entrypointTable;
381             }
382 
383             /**
384              * The entrypoint method delegates to helper methods for each of the types of tables
385              * found in the currency data.
386              */
387             @Override
put(UResource.Key key, UResource.Value value, boolean isRoot)388             public void put(UResource.Key key, UResource.Value value, boolean isRoot) {
389                 if (noRoot && isRoot) {
390                     // Don't consume the root bundle
391                     return;
392                 }
393 
394                 switch (entrypointTable) {
395                 case TOP:
396                     consumeTopTable(key, value);
397                     break;
398                 case CURRENCIES:
399                     consumeCurrenciesEntry(key, value);
400                     break;
401                 case CURRENCY_PLURALS:
402                     consumeCurrencyPluralsEntry(key, value);
403                     break;
404                 case CURRENCY_VARIANT:
405                     consumeCurrenciesVariantEntry(key, value);
406                     break;
407                 case CURRENCY_SPACING:
408                     consumeCurrencySpacingTable(key, value);
409                     break;
410                 case CURRENCY_UNIT_PATTERNS:
411                     consumeCurrencyUnitPatternsTable(key, value);
412                     break;
413                 }
414             }
415 
consumeTopTable(UResource.Key key, UResource.Value value)416             private void consumeTopTable(UResource.Key key, UResource.Value value) {
417                 UResource.Table table = value.getTable();
418                 for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
419                     if (key.contentEquals("Currencies")) {
420                         consumeCurrenciesTable(key, value);
421                     } else if (key.contentEquals("Currencies%variant")) {
422                         consumeCurrenciesVariantTable(key, value);
423                     } else if (key.contentEquals("CurrencyPlurals")) {
424                         consumeCurrencyPluralsTable(key, value);
425                     }
426                 }
427             }
428 
429             /*
430              *  Currencies{
431              *      ...
432              *      USD{
433              *          "US$",        => symbol
434              *          "US Dollar",  => display name
435              *      }
436              *      ...
437              *      ESP{
438              *          "₧",                  => symbol
439              *          "pesseta espanyola",  => display name
440              *          {
441              *              "¤ #,##0.00",     => currency-specific pattern
442              *              ",",              => currency-specific grouping separator
443              *              ".",              => currency-specific decimal separator
444              *          }
445              *      }
446              *      ...
447              *  }
448              */
consumeCurrenciesTable(UResource.Key key, UResource.Value value)449             void consumeCurrenciesTable(UResource.Key key, UResource.Value value) {
450                 // The full Currencies table is consumed for parsing only.
451                 assert parsingData != null;
452                 UResource.Table table = value.getTable();
453                 for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
454                     String isoCode = key.toString();
455                     if (value.getType() != UResourceBundle.ARRAY) {
456                         throw new ICUException("Unexpected data type in Currencies table for " + isoCode);
457                     }
458                     UResource.Array array = value.getArray();
459 
460                     parsingData.symbolToIsoCode.put(isoCode, isoCode); // Add the ISO code itself as a symbol
461                     array.getValue(0, value);
462                     parsingData.symbolToIsoCode.put(value.getString(), isoCode);
463                     array.getValue(1, value);
464                     parsingData.nameToIsoCode.put(value.getString(), isoCode);
465                 }
466             }
467 
consumeCurrenciesEntry(UResource.Key key, UResource.Value value)468             void consumeCurrenciesEntry(UResource.Key key, UResource.Value value) {
469                 assert formattingData != null;
470                 String isoCode = key.toString();
471                 if (value.getType() != UResourceBundle.ARRAY) {
472                     throw new ICUException("Unexpected data type in Currencies table for " + isoCode);
473                 }
474                 UResource.Array array = value.getArray();
475 
476                 if (formattingData.symbol == null) {
477                     array.getValue(0, value);
478                     formattingData.symbol = value.getString();
479                 }
480                 if (formattingData.displayName == null) {
481                     array.getValue(1, value);
482                     formattingData.displayName = value.getString();
483                 }
484 
485                 // If present, the third element is the currency format info.
486                 // TODO: Write unit test to ensure that this data is being used by number formatting.
487                 if (array.getSize() > 2 && formattingData.formatInfo == null) {
488                     array.getValue(2, value);
489                     UResource.Array formatArray = value.getArray();
490                     formatArray.getValue(0, value);
491                     String formatPattern = value.getString();
492                     formatArray.getValue(1, value);
493                     String decimalSeparator = value.getString();
494                     formatArray.getValue(2, value);
495                     String groupingSeparator = value.getString();
496                     formattingData.formatInfo = new CurrencyFormatInfo(
497                             isoCode, formatPattern, decimalSeparator, groupingSeparator);
498                 }
499             }
500 
501             /*
502              *  Currencies%narrow{
503              *      AOA{"Kz"}
504              *      ARS{"$"}
505              *      ...
506              *  }
507              */
consumeCurrenciesVariantEntry(UResource.Key key, UResource.Value value)508             void consumeCurrenciesVariantEntry(UResource.Key key, UResource.Value value) {
509                 assert variantSymbol != null;
510                 // No extra structure to traverse.
511                 if (variantSymbol.symbol == null) {
512                     variantSymbol.symbol = value.getString();
513                 }
514             }
515 
516             /*
517              *  Currencies%variant{
518              *      TRY{"TL"}
519              *  }
520              */
consumeCurrenciesVariantTable(UResource.Key key, UResource.Value value)521             void consumeCurrenciesVariantTable(UResource.Key key, UResource.Value value) {
522                 // Note: This data is used for parsing but not formatting.
523                 assert parsingData != null;
524                 UResource.Table table = value.getTable();
525                 for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
526                     String isoCode = key.toString();
527                     parsingData.symbolToIsoCode.put(value.getString(), isoCode);
528                 }
529             }
530 
531             /*
532              *  CurrencyPlurals{
533              *      BYB{
534              *          one{"Belarusian new rouble (1994–1999)"}
535              *          other{"Belarusian new roubles (1994–1999)"}
536              *      }
537              *      ...
538              *  }
539              */
consumeCurrencyPluralsTable(UResource.Key key, UResource.Value value)540             void consumeCurrencyPluralsTable(UResource.Key key, UResource.Value value) {
541                 // The full CurrencyPlurals table is consumed for parsing only.
542                 assert parsingData != null;
543                 UResource.Table table = value.getTable();
544                 for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
545                     String isoCode = key.toString();
546                     UResource.Table pluralsTable = value.getTable();
547                     for (int j=0; pluralsTable.getKeyAndValue(j, key, value); j++) {
548                         StandardPlural plural = StandardPlural.orNullFromString(key.toString());
549                         if (plural == null) {
550                             throw new ICUException("Could not make StandardPlural from keyword " + key);
551                         }
552 
553                         parsingData.nameToIsoCode.put(value.getString(), isoCode);
554                     }
555                 }
556             }
557 
consumeCurrencyPluralsEntry(UResource.Key key, UResource.Value value)558             void consumeCurrencyPluralsEntry(UResource.Key key, UResource.Value value) {
559                 assert pluralsData != null;
560                 UResource.Table pluralsTable = value.getTable();
561                 for (int j=0; pluralsTable.getKeyAndValue(j, key, value); j++) {
562                     StandardPlural plural = StandardPlural.orNullFromString(key.toString());
563                     if (plural == null) {
564                         throw new ICUException("Could not make StandardPlural from keyword " + key);
565                     }
566 
567                     if (pluralsData[1 + plural.ordinal()] == null) {
568                         pluralsData[1 + plural.ordinal()] = value.getString();
569                     }
570                 }
571             }
572 
573             /*
574              *  currencySpacing{
575              *      afterCurrency{
576              *          currencyMatch{"[:^S:]"}
577              *          insertBetween{" "}
578              *          surroundingMatch{"[:digit:]"}
579              *      }
580              *      beforeCurrency{
581              *          currencyMatch{"[:^S:]"}
582              *          insertBetween{" "}
583              *          surroundingMatch{"[:digit:]"}
584              *      }
585              *  }
586              */
consumeCurrencySpacingTable(UResource.Key key, UResource.Value value)587             void consumeCurrencySpacingTable(UResource.Key key, UResource.Value value) {
588                 assert spacingInfo != null;
589                 UResource.Table spacingTypesTable = value.getTable();
590                 for (int i = 0; spacingTypesTable.getKeyAndValue(i, key, value); ++i) {
591                     CurrencySpacingInfo.SpacingType type;
592                     if (key.contentEquals("beforeCurrency")) {
593                         type = CurrencySpacingInfo.SpacingType.BEFORE;
594                         spacingInfo.hasBeforeCurrency = true;
595                     } else if (key.contentEquals("afterCurrency")) {
596                         type = CurrencySpacingInfo.SpacingType.AFTER;
597                         spacingInfo.hasAfterCurrency = true;
598                     } else {
599                         continue;
600                     }
601 
602                     UResource.Table patternsTable = value.getTable();
603                     for (int j = 0; patternsTable.getKeyAndValue(j, key, value); ++j) {
604                         CurrencySpacingInfo.SpacingPattern pattern;
605                         if (key.contentEquals("currencyMatch")) {
606                             pattern = CurrencySpacingInfo.SpacingPattern.CURRENCY_MATCH;
607                         } else if (key.contentEquals("surroundingMatch")) {
608                             pattern = CurrencySpacingInfo.SpacingPattern.SURROUNDING_MATCH;
609                         } else if (key.contentEquals("insertBetween")) {
610                             pattern = CurrencySpacingInfo.SpacingPattern.INSERT_BETWEEN;
611                         } else {
612                             continue;
613                         }
614 
615                         spacingInfo.setSymbolIfNull(type, pattern, value.getString());
616                     }
617                 }
618             }
619 
620             /*
621              *  CurrencyUnitPatterns{
622              *      other{"{0} {1}"}
623              *      ...
624              *  }
625              */
consumeCurrencyUnitPatternsTable(UResource.Key key, UResource.Value value)626             void consumeCurrencyUnitPatternsTable(UResource.Key key, UResource.Value value) {
627                 assert unitPatterns != null;
628                 UResource.Table table = value.getTable();
629                 for (int i = 0; table.getKeyAndValue(i, key, value); i++) {
630                     String pluralKeyword = key.toString();
631                     if (unitPatterns.get(pluralKeyword) == null) {
632                         unitPatterns.put(pluralKeyword, value.getString());
633                     }
634                 }
635             }
636         }
637     }
638 }
639