/*
*******************************************************************************
- * Copyright (C) 2008-2010, International Business Machines Corporation and *
+ * Copyright (C) 2008-2013, International Business Machines Corporation and *
* others. All Rights Reserved. *
*******************************************************************************
*/
import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.MissingResourceException;
+import java.util.Set;
import com.ibm.icu.impl.ICUCache;
import com.ibm.icu.impl.ICUResourceBundle;
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
-
/**
* DateIntervalInfo is a public class for encapsulating localizable
* date time interval patterns. It is used by DateIntervalFormat.
private Map<String, Map<String, PatternInfo>> fIntervalPatterns = null;
private transient boolean frozen = false;
+
+ // If true, fIntervalPatterns should not be modified in-place because it
+ // is shared with other objects. Unlike frozen which is always true once
+ // set to true, this field can go from true to false as long as frozen is
+ // false.
+ private transient boolean fIntervalPatternsReadOnly = false;
/**
if ( dii == null ) {
// initialize data from scratch
setup(locale);
- // TODO: should put a clone in cache?
- // or put itself in cache?
- // DIICACHE.put(key, this);
- dii = (DateIntervalInfo)this.clone();
- DIICACHE.put(key, dii);
+ // Marking fIntervalPatterns read-only makes cloning cheaper.
+ fIntervalPatternsReadOnly = true;
+ // We freeze what goes in the cache without freezing this object.
+ DIICACHE.put(key, ((DateIntervalInfo) clone()).freeze());
} else {
- initializeData(dii);
+ initializeFromReadOnlyPatterns(dii);
}
}
- /*
- * Initialize DateIntervalInfo from another instance
- * @param dii an DateIntervalInfo instance
+ /**
+ * Initialize this object
+ * @param dii must have read-only fIntervalPatterns.
*/
- private void initializeData(DateIntervalInfo dii) {
+ private void initializeFromReadOnlyPatterns(DateIntervalInfo dii) {
fFallbackIntervalPattern = dii.fFallbackIntervalPattern;
fFirstDateInPtnIsLaterDate = dii.fFirstDateInPtnIsLaterDate;
fIntervalPatterns = dii.fIntervalPatterns;
+ fIntervalPatternsReadOnly = true;
}
try {
// loop through all locales to get all available skeletons'
// interval format
- ULocale parentLocale = locale;
+ ULocale currentLocale = locale;
// Get the correct calendar type
String calendarTypeToUse = locale.getKeywordValue("calendar");
if ( calendarTypeToUse == null ) {
calendarTypeToUse = "gregorian"; // fallback
}
do {
- String name = parentLocale.getName();
+ String name = currentLocale.getName();
if ( name.length() == 0 ) {
break;
}
- ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.
- getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,locale);
- rb = rb.getWithFallback("calendar");
- ICUResourceBundle calTypeBundle = rb.getWithFallback(
- calendarTypeToUse);
- ICUResourceBundle itvDtPtnResource =calTypeBundle.
- getWithFallback("intervalFormats");
+ ICUResourceBundle rb = (ICUResourceBundle) UResourceBundle.getBundleInstance(ICUResourceBundle.ICU_BASE_NAME,currentLocale);
+ // Note:
+ // ICU4J getWithFallback does not work well when
+ // 1) A nested table is an alias to /LOCALE/...
+ // 2) getWithFallback is called multiple times for going down hierarchical resource path
+ // #9987 resolved the issue of alias table when full path is specified in getWithFallback,
+ // but there is no easy solution when the equivalent operation is done by multiple operations.
+ // This issue is addressed in #9964.
+// ICUResourceBundle calBundle = rb.getWithFallback("calendar");
+// ICUResourceBundle calTypeBundle = calBundle.getWithFallback(calendarTypeToUse);
+ ICUResourceBundle itvDtPtnResource =rb.getWithFallback("calendar/" + calendarTypeToUse + "/intervalFormats");
// look for fallback first, since it establishes the default order
- String fallback = itvDtPtnResource.getStringWithFallback(
- FALLBACK_STRING);
+ String fallback = itvDtPtnResource.getStringWithFallback(FALLBACK_STRING);
setFallbackIntervalPattern(fallback);
int size = itvDtPtnResource.getSize();
for ( int index = 0; index < size; ++index ) {
if ( skeleton.compareTo(FALLBACK_STRING) == 0 ) {
continue;
}
- ICUResourceBundle intervalPatterns =
- itvDtPtnResource.getWithFallback(skeleton);
+ ICUResourceBundle intervalPatterns = (ICUResourceBundle)itvDtPtnResource.get(skeleton);
int ptnNum = intervalPatterns.getSize();
for ( int ptnIndex = 0; ptnIndex < ptnNum; ++ptnIndex) {
String key = intervalPatterns.get(ptnIndex).getKey();
}
}
}
- parentLocale = parentLocale.getFallback();
- } while (parentLocale != null && !parentLocale.equals(ULocale.ROOT));
+ try {
+ UResourceBundle parentNameBundle = rb.get("%%Parent");
+ currentLocale = new ULocale(parentNameBundle.getString());
+ } catch (MissingResourceException e) {
+ currentLocale = currentLocale.getFallback();
+ }
+ } while (currentLocale != null && !currentLocale.getBaseName().equals("root"));
} catch ( MissingResourceException e) {
// ok, will fallback to {data0} - {date1}
}
if ( lrgDiffCalUnit > MINIMUM_SUPPORTED_CALENDAR_FIELD ) {
throw new IllegalArgumentException("calendar field is larger than MINIMUM_SUPPORTED_CALENDAR_FIELD");
}
-
+ if (fIntervalPatternsReadOnly) {
+ fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns);
+ fIntervalPatternsReadOnly = false;
+ }
PatternInfo ptnInfo = setIntervalPatternInternally(skeleton,
CALENDAR_FIELD_TO_PATTERN_LETTER[lrgDiffCalUnit],
intervalPattern);
DateIntervalInfo other = (DateIntervalInfo) super.clone();
other.fFallbackIntervalPattern=fFallbackIntervalPattern;
other.fFirstDateInPtnIsLaterDate = fFirstDateInPtnIsLaterDate;
- other.fIntervalPatterns = new HashMap<String, Map<String, PatternInfo>>();
- for (String skeleton : fIntervalPatterns.keySet()) {
- Map<String, PatternInfo> patternsOfOneSkeleton = fIntervalPatterns.get(skeleton);
- Map<String, PatternInfo> oneSetPtn = new HashMap<String, PatternInfo>();
- for (String calField : patternsOfOneSkeleton.keySet()) {
- PatternInfo value = patternsOfOneSkeleton.get(calField);
- oneSetPtn.put(calField, value);
- }
- other.fIntervalPatterns.put(skeleton, oneSetPtn);
+ if (fIntervalPatternsReadOnly) {
+ other.fIntervalPatterns = fIntervalPatterns;
+ other.fIntervalPatternsReadOnly = true;
+ } else {
+ other.fIntervalPatterns = cloneIntervalPatterns(fIntervalPatterns);
+ other.fIntervalPatternsReadOnly = false;
}
other.frozen = false;
return other;
///CLOVER:ON
}
}
+
+ private static Map<String, Map<String, PatternInfo>> cloneIntervalPatterns(
+ Map<String, Map<String, PatternInfo>> patterns) {
+ Map<String, Map<String, PatternInfo>> result = new HashMap<String, Map<String, PatternInfo>>();
+ for (Entry<String, Map<String, PatternInfo>> skeletonEntry : patterns.entrySet()) {
+ String skeleton = skeletonEntry.getKey();
+ Map<String, PatternInfo> patternsOfOneSkeleton = skeletonEntry.getValue();
+ Map<String, PatternInfo> oneSetPtn = new HashMap<String, PatternInfo>();
+ for (Entry<String, PatternInfo> calEntry : patternsOfOneSkeleton.entrySet()) {
+ String calField = calEntry.getKey();
+ PatternInfo value = calEntry.getValue();
+ oneSetPtn.put(calField, value);
+ }
+ result.put(skeleton, oneSetPtn);
+ }
+ return result;
+ }
+
/**
*/
public DateIntervalInfo freeze() {
frozen = true;
+ fIntervalPatternsReadOnly = true;
return this;
}
public int hashCode() {
return fIntervalPatterns.hashCode();
}
+
+ /**
+ * @internal CLDR
+ * @deprecated This API is ICU internal only.
+ */
+ public Map<String,Set<String>> getPatterns() {
+ LinkedHashMap<String,Set<String>> result = new LinkedHashMap<String,Set<String>>();
+ for (Entry<String, Map<String, PatternInfo>> entry : fIntervalPatterns.entrySet()) {
+ result.put(entry.getKey(), new LinkedHashSet<String>(entry.getValue().keySet()));
+ }
+ return result;
+ }
}// end class DateIntervalInfo