1 package be
.nikiroo
.utils
.resources
;
3 import java
.io
.BufferedWriter
;
5 import java
.io
.FileInputStream
;
6 import java
.io
.FileOutputStream
;
7 import java
.io
.IOException
;
8 import java
.io
.InputStreamReader
;
9 import java
.io
.OutputStreamWriter
;
10 import java
.io
.Reader
;
11 import java
.io
.Writer
;
12 import java
.lang
.reflect
.Field
;
13 import java
.util
.ArrayList
;
14 import java
.util
.HashMap
;
15 import java
.util
.List
;
16 import java
.util
.Locale
;
18 import java
.util
.MissingResourceException
;
19 import java
.util
.PropertyResourceBundle
;
20 import java
.util
.ResourceBundle
;
22 import be
.nikiroo
.utils
.resources
.Meta
.Format
;
25 * This class encapsulate a {@link ResourceBundle} in UTF-8. It allows to
26 * retrieve values associated to an enumeration, and allows some additional
29 * It also sports a writable change map, and you can save back the
30 * {@link Bundle} to file with {@link Bundle#updateFile(String)}.
33 * the enum to use to get values out of this class
38 public class Bundle
<E
extends Enum
<E
>> {
40 protected Class
<E
> type
;
42 * The {@link Enum} associated to this {@link Bundle} (all the keys used in
43 * this {@link Bundle} will be of this type).
45 protected Enum
<?
> keyType
;
47 private TransBundle
<E
> descriptionBundle
;
50 private Map
<String
, String
> map
;
52 private Map
<String
, String
> changeMap
;
55 * Create a new {@link Bundles} of the given name.
58 * a runtime instance of the class of E
60 * the name of the {@link Bundles}
61 * @param descriptionBundle
62 * the description {@link TransBundle}, that is, a
63 * {@link TransBundle} dedicated to the description of the values
64 * of the given {@link Bundle} (can be NULL)
66 protected Bundle(Class
<E
> type
, Enum
<?
> name
,
67 TransBundle
<E
> descriptionBundle
) {
70 this.descriptionBundle
= descriptionBundle
;
72 this.map
= new HashMap
<String
, String
>();
73 this.changeMap
= new HashMap
<String
, String
>();
74 setBundle(name
, Locale
.getDefault(), false);
78 * Check if the setting is set into this {@link Bundle}.
81 * the id of the setting to check
82 * @param includeDefaultValue
83 * TRUE to only return false when the setting is not set AND
84 * there is no default value
86 * @return TRUE if the setting is set
88 public boolean isSet(E id
, boolean includeDefaultValue
) {
89 return isSet(id
.name(), includeDefaultValue
);
93 * Check if the setting is set into this {@link Bundle}.
96 * the id of the setting to check
97 * @param includeDefaultValue
98 * TRUE to only return false when the setting is explicitly set
99 * to NULL (and not just "no set") in the change maps
101 * @return TRUE if the setting is set
103 protected boolean isSet(String name
, boolean includeDefaultValue
) {
104 if (getString(name
, null) == null) {
105 if (!includeDefaultValue
|| getString(name
, "") == null) {
114 * Return the value associated to the given id as a {@link String}.
117 * the id of the value to get
119 * @return the associated value, or NULL if not found (not present in the
122 public String
getString(E id
) {
123 return getString(id
, null);
127 * Return the value associated to the given id as a {@link String}.
129 * If no value is associated, take the default one if any.
132 * the id of the value to get
134 * the default value when it is not present in the config file
136 * @return the associated value, or <tt>def</tt> if not found (not present
137 * in the resource file)
139 public String
getString(E id
, String def
) {
140 return getString(id
, def
, -1);
144 * Return the value associated to the given id as a {@link String}.
146 * If no value is associated (or if it is empty!), take the default one if
150 * the id of the value to get
152 * the default value when it is not present in the config file
154 * the item number to get for an array of values, or -1 for
157 * @return the associated value, <tt>def</tt> if not found (not present in
158 * the resource file) or NULL if the item is specified (not -1) and
161 public String
getString(E id
, String def
, int item
) {
162 String rep
= getString(id
.name(), null);
164 rep
= getMetaDef(id
.name());
172 List
<String
> values
= BundleHelper
.parseList(rep
, item
);
173 if (values
!= null && item
< values
.size()) {
174 return values
.get(item
);
184 * Set the value associated to the given id as a {@link String}.
187 * the id of the value to set
192 public void setString(E id
, String value
) {
193 setString(id
.name(), value
);
197 * Set the value associated to the given id as a {@link String}.
200 * the id of the value to set
204 * the item number to get for an array of values, or -1 for
208 public void setString(E id
, String value
, int item
) {
210 setString(id
.name(), value
);
212 List
<String
> values
= getList(id
);
213 setString(id
.name(), BundleHelper
.fromList(values
, value
, item
));
218 * Return the value associated to the given id as a {@link String} suffixed
219 * with the runtime value "_suffix" (that is, "_" and suffix).
221 * Will only accept suffixes that form an existing id.
223 * If no value is associated, take the default one if any.
226 * the id of the value to get
230 * @return the associated value, or NULL if not found (not present in the
233 public String
getStringX(E id
, String suffix
) {
234 return getStringX(id
, suffix
, null, -1);
238 * Return the value associated to the given id as a {@link String} suffixed
239 * with the runtime value "_suffix" (that is, "_" and suffix).
241 * Will only accept suffixes that form an existing id.
243 * If no value is associated, take the default one if any.
246 * the id of the value to get
250 * the default value when it is not present in the config file
252 * @return the associated value, or NULL if not found (not present in the
255 public String
getStringX(E id
, String suffix
, String def
) {
256 return getStringX(id
, suffix
, def
, -1);
260 * Return the value associated to the given id as a {@link String} suffixed
261 * with the runtime value "_suffix" (that is, "_" and suffix).
263 * Will only accept suffixes that form an existing id.
265 * If no value is associated, take the default one if any.
268 * the id of the value to get
272 * the default value when it is not present in the config file
274 * the item number to get for an array of values, or -1 for
277 * @return the associated value, <tt>def</tt> if not found (not present in
278 * the resource file), NULL if the item is specified (not -1) but
279 * does not exist and NULL if bad key
281 public String
getStringX(E id
, String suffix
, String def
, int item
) {
282 String key
= id
.name()
283 + (suffix
== null ?
"" : "_" + suffix
.toUpperCase());
286 id
= Enum
.valueOf(type
, key
);
287 return getString(id
, def
, item
);
288 } catch (IllegalArgumentException e
) {
295 * Set the value associated to the given id as a {@link String} suffixed
296 * with the runtime value "_suffix" (that is, "_" and suffix).
298 * Will only accept suffixes that form an existing id.
301 * the id of the value to set
307 public void setStringX(E id
, String suffix
, String value
) {
308 setStringX(id
, suffix
, value
, -1);
312 * Set the value associated to the given id as a {@link String} suffixed
313 * with the runtime value "_suffix" (that is, "_" and suffix).
315 * Will only accept suffixes that form an existing id.
318 * the id of the value to set
324 * the item number to get for an array of values, or -1 for
327 public void setStringX(E id
, String suffix
, String value
, int item
) {
328 String key
= id
.name()
329 + (suffix
== null ?
"" : "_" + suffix
.toUpperCase());
332 id
= Enum
.valueOf(type
, key
);
333 setString(id
, value
, item
);
334 } catch (IllegalArgumentException e
) {
339 * Return the value associated to the given id as a {@link Boolean}.
341 * If no value is associated, take the default one if any.
344 * the id of the value to get
346 * @return the associated value
348 public Boolean
getBoolean(E id
) {
349 return BundleHelper
.parseBoolean(getString(id
), -1);
353 * Return the value associated to the given id as a {@link Boolean}.
355 * If no value is associated, take the default one if any.
358 * the id of the value to get
360 * the default value when it is not present in the config file or
361 * if it is not a boolean value
363 * @return the associated value
365 public boolean getBoolean(E id
, boolean def
) {
366 Boolean value
= getBoolean(id
);
375 * Return the value associated to the given id as a {@link Boolean}.
377 * If no value is associated, take the default one if any.
380 * the id of the value to get
382 * the default value when it is not present in the config file or
383 * if it is not a boolean value
385 * the item number to get for an array of values, or -1 for
388 * @return the associated value
390 public Boolean
getBoolean(E id
, boolean def
, int item
) {
391 String value
= getString(id
);
393 return BundleHelper
.parseBoolean(value
, item
);
400 * Set the value associated to the given id as a {@link Boolean}.
403 * the id of the value to set
408 public void setBoolean(E id
, boolean value
) {
409 setBoolean(id
, value
, -1);
413 * Set the value associated to the given id as a {@link Boolean}.
416 * the id of the value to set
420 * the item number to get for an array of values, or -1 for
424 public void setBoolean(E id
, boolean value
, int item
) {
425 setString(id
, BundleHelper
.fromBoolean(value
), item
);
429 * Return the value associated to the given id as an {@link Integer}.
431 * If no value is associated, take the default one if any.
434 * the id of the value to get
436 * @return the associated value
438 public Integer
getInteger(E id
) {
439 String value
= getString(id
);
441 return BundleHelper
.parseInteger(value
, -1);
448 * Return the value associated to the given id as an int.
450 * If no value is associated, take the default one if any.
453 * the id of the value to get
455 * the default value when it is not present in the config file or
456 * if it is not a int value
458 * @return the associated value
460 public int getInteger(E id
, int def
) {
461 Integer value
= getInteger(id
);
470 * Return the value associated to the given id as an int.
472 * If no value is associated, take the default one if any.
475 * the id of the value to get
477 * the default value when it is not present in the config file or
478 * if it is not a int value
480 * the item number to get for an array of values, or -1 for
483 * @return the associated value
485 public Integer
getInteger(E id
, int def
, int item
) {
486 String value
= getString(id
);
488 return BundleHelper
.parseInteger(value
, item
);
495 * Set the value associated to the given id as a {@link Integer}.
498 * the id of the value to set
503 public void setInteger(E id
, int value
) {
504 setInteger(id
, value
, -1);
508 * Set the value associated to the given id as a {@link Integer}.
511 * the id of the value to set
515 * the item number to get for an array of values, or -1 for
519 public void setInteger(E id
, int value
, int item
) {
520 setString(id
, BundleHelper
.fromInteger(value
), item
);
524 * Return the value associated to the given id as a {@link Character}.
526 * If no value is associated, take the default one if any.
529 * the id of the value to get
531 * @return the associated value
533 public Character
getCharacter(E id
) {
534 return BundleHelper
.parseCharacter(getString(id
), -1);
538 * Return the value associated to the given id as a {@link Character}.
540 * If no value is associated, take the default one if any.
543 * the id of the value to get
545 * the default value when it is not present in the config file or
546 * if it is not a char value
548 * @return the associated value
550 public char getCharacter(E id
, char def
) {
551 Character value
= getCharacter(id
);
560 * Return the value associated to the given id as a {@link Character}.
562 * If no value is associated, take the default one if any.
565 * the id of the value to get
567 * the default value when it is not present in the config file or
568 * if it is not a char value
570 * the item number to get for an array of values, or -1 for
573 * @return the associated value
575 public Character
getCharacter(E id
, char def
, int item
) {
576 String value
= getString(id
);
578 return BundleHelper
.parseCharacter(value
, item
);
585 * Set the value associated to the given id as a {@link Character}.
588 * the id of the value to set
593 public void setCharacter(E id
, char value
) {
594 setCharacter(id
, value
, -1);
598 * Set the value associated to the given id as a {@link Character}.
601 * the id of the value to set
605 * the item number to get for an array of values, or -1 for
609 public void setCharacter(E id
, char value
, int item
) {
610 setString(id
, BundleHelper
.fromCharacter(value
), item
);
614 * Return the value associated to the given id as a colour if it is found
617 * The returned value is an ARGB value.
619 * If no value is associated, take the default one if any.
622 * the id of the value to get
624 * @return the associated value
626 public Integer
getColor(E id
) {
627 return BundleHelper
.parseColor(getString(id
), -1);
631 * Return the value associated to the given id as a colour if it is found
634 * The returned value is an ARGB value.
636 * If no value is associated, take the default one if any.
639 * the id of the value to get
641 * the default value when it is not present in the config file or
642 * if it is not a char value
644 * @return the associated value
646 public int getColor(E id
, int def
) {
647 Integer value
= getColor(id
);
656 * Return the value associated to the given id as a colour if it is found
659 * The returned value is an ARGB value.
661 * If no value is associated, take the default one if any.
664 * the id of the value to get
666 * the default value when it is not present in the config file or
667 * if it is not a char value
669 * the item number to get for an array of values, or -1 for
672 * @return the associated value
674 public Integer
getColor(E id
, int def
, int item
) {
675 String value
= getString(id
);
677 return BundleHelper
.parseColor(value
, item
);
684 * Set the value associated to the given id as a colour.
686 * The value is a BGRA value.
689 * the id of the value to set
693 public void setColor(E id
, Integer color
) {
694 setColor(id
, color
, -1);
698 * Set the value associated to the given id as a Color.
701 * the id of the value to set
705 * the item number to get for an array of values, or -1 for
709 public void setColor(E id
, int value
, int item
) {
710 setString(id
, BundleHelper
.fromColor(value
), item
);
714 * Return the value associated to the given id as a list of values if it is
715 * found and can be parsed.
717 * If no value is associated, take the default one if any.
720 * the id of the value to get
722 * @return the associated list, empty if the value is empty, NULL if it is
723 * not found or cannot be parsed as a list
725 public List
<String
> getList(E id
) {
726 return BundleHelper
.parseList(getString(id
), -1);
730 * Return the value associated to the given id as a list of values if it is
731 * found and can be parsed.
733 * If no value is associated, take the default one if any.
736 * the id of the value to get
738 * the default value when it is not present in the config file or
739 * if it is not a char value
741 * @return the associated list, empty if the value is empty, NULL if it is
742 * not found or cannot be parsed as a list
744 public List
<String
> getList(E id
, List
<String
> def
) {
745 List
<String
> value
= getList(id
);
754 * Return the value associated to the given id as a list of values if it is
755 * found and can be parsed.
757 * If no value is associated, take the default one if any.
760 * the id of the value to get
762 * the default value when it is not present in the config file or
763 * if it is not a char value
765 * the item number to get for an array of values, or -1 for
768 * @return the associated list, empty if the value is empty, NULL if it is
769 * not found or cannot be parsed as a list
771 public List
<String
> getList(E id
, List
<String
> def
, int item
) {
772 String value
= getString(id
);
774 return BundleHelper
.parseList(value
, item
);
781 * Set the value associated to the given id as a list of values.
784 * the id of the value to set
786 * the new list of values
788 public void setList(E id
, List
<String
> list
) {
789 setList(id
, list
, -1);
793 * Set the value associated to the given id as a {@link List}.
796 * the id of the value to set
800 * the item number to get for an array of values, or -1 for
804 public void setList(E id
, List
<String
> value
, int item
) {
805 setString(id
, BundleHelper
.fromList(value
), item
);
809 * Create/update the .properties file.
811 * Will use the most likely candidate as base if the file does not already
812 * exists and this resource is translatable (for instance, "en_US" will use
813 * "en" as a base if the resource is a translation file).
815 * Will update the files in {@link Bundles#getDirectory()}; it <b>MUST</b>
818 * @throws IOException
819 * in case of IO errors
821 public void updateFile() throws IOException
{
822 updateFile(Bundles
.getDirectory());
826 * Create/update the .properties file.
828 * Will use the most likely candidate as base if the file does not already
829 * exists and this resource is translatable (for instance, "en_US" will use
830 * "en" as a base if the resource is a translation file).
833 * the path where the .properties files are, <b>MUST NOT</b> be
836 * @throws IOException
837 * in case of IO errors
839 public void updateFile(String path
) throws IOException
{
840 File file
= getUpdateFile(path
);
842 BufferedWriter writer
= new BufferedWriter(new OutputStreamWriter(
843 new FileOutputStream(file
), "UTF-8"));
849 for (Field field
: type
.getDeclaredFields()) {
850 Meta meta
= field
.getAnnotation(Meta
.class);
852 E id
= Enum
.valueOf(type
, field
.getName());
853 String info
= getMetaInfo(meta
);
860 writeValue(writer
, id
);
868 * Delete the .properties file.
870 * Will use the most likely candidate as base if the file does not already
871 * exists and this resource is translatable (for instance, "en_US" will use
872 * "en" as a base if the resource is a translation file).
874 * Will delete the files in {@link Bundles#getDirectory()}; it <b>MUST</b>
877 * @return TRUE if the file was deleted
879 public boolean deleteFile() {
880 return deleteFile(Bundles
.getDirectory());
884 * Delete the .properties file.
886 * Will use the most likely candidate as base if the file does not already
887 * exists and this resource is translatable (for instance, "en_US" will use
888 * "en" as a base if the resource is a translation file).
891 * the path where the .properties files are, <b>MUST NOT</b> be
894 * @return TRUE if the file was deleted
896 public boolean deleteFile(String path
) {
897 File file
= getUpdateFile(path
);
898 return file
.delete();
902 * The description {@link TransBundle}, that is, a {@link TransBundle}
903 * dedicated to the description of the values of the given {@link Bundle}
906 * @return the description {@link TransBundle}
908 public TransBundle
<E
> getDescriptionBundle() {
909 return descriptionBundle
;
913 * Reload the {@link Bundle} data files.
915 * @param resetToDefault
916 * reset to the default configuration (do not look into the
917 * possible user configuration files, only take the original
920 public void reload(boolean resetToDefault
) {
921 setBundle(keyType
, Locale
.getDefault(), resetToDefault
);
925 * Check if the internal map contains the given key.
928 * the key to check for
930 * @return true if it does
932 protected boolean containsKey(String key
) {
933 return changeMap
.containsKey(key
) || map
.containsKey(key
);
937 * The default {@link Meta#def()} value for the given enumeration name.
940 * the enumeration name (the "id")
942 * @return the def value in the {@link MetaInfo} or "" if none (never NULL)
944 protected String
getMetaDef(String id
) {
947 Meta meta
= type
.getDeclaredField(id
).getAnnotation(Meta
.class);
949 } catch (NoSuchFieldException e
) {
950 } catch (SecurityException e
) {
961 * Get the value for the given key if it exists in the internal map, or
962 * <tt>def</tt> if not.
964 * DO NOT get the default meta value (MetaInfo.def()).
967 * the key to check for
969 * the default value when it is not present in the internal map
971 * @return the value, or <tt>def</tt> if not found
973 protected String
getString(String key
, String def
) {
974 if (changeMap
.containsKey(key
)) {
975 return changeMap
.get(key
);
978 if (map
.containsKey(key
)) {
986 * Set the value for this key, in the change map (it is kept in memory, not
992 * the associated value
994 protected void setString(String key
, String value
) {
995 changeMap
.put(key
, value
== null ?
null : value
.trim());
999 * Return formated, display-able information from the {@link Meta} field
1000 * given. Each line will always starts with a "#" character.
1003 * the {@link Meta} field
1005 * @return the information to display or NULL if none
1007 protected String
getMetaInfo(Meta meta
) {
1008 String desc
= meta
.description();
1009 boolean group
= meta
.group();
1010 Meta
.Format format
= meta
.format();
1011 String
[] list
= meta
.list();
1012 boolean nullable
= meta
.nullable();
1013 String def
= meta
.def();
1014 boolean array
= meta
.array();
1016 // Default, empty values -> NULL
1017 if (desc
.length() + list
.length
+ def
.length() == 0 && !group
1018 && nullable
&& format
== Format
.STRING
) {
1022 StringBuilder builder
= new StringBuilder();
1023 for (String line
: desc
.split("\n")) {
1024 builder
.append("# ").append(line
).append("\n");
1028 builder
.append("# This item is used as a group, its content is not expected to be used.");
1030 builder
.append("# (FORMAT: ").append(format
)
1031 .append(nullable ?
"" : ", required");
1032 builder
.append(") ");
1034 if (list
.length
> 0) {
1035 builder
.append("\n# ALLOWED VALUES: ");
1036 boolean first
= true;
1037 for (String value
: list
) {
1039 builder
.append(", ");
1041 builder
.append(BundleHelper
.escape(value
));
1047 builder
.append("\n# (This item accepts a list of ^escaped comma-separated values)");
1051 return builder
.toString();
1055 * The display name used in the <tt>.properties file</tt>.
1059 protected String
getBundleDisplayName() {
1060 return keyType
.toString();
1064 * Write the header found in the configuration <tt>.properties</tt> file of
1065 * this {@link Bundles}.
1068 * the {@link Writer} to write the header in
1070 * @throws IOException
1071 * in case of IO error
1073 protected void writeHeader(Writer writer
) throws IOException
{
1074 writer
.write("# " + getBundleDisplayName() + "\n");
1075 writer
.write("#\n");
1079 * Write the given data to the config file, i.e., "MY_ID = my_curent_value"
1080 * followed by a new line.
1082 * Will prepend a # sign if the is is not set (see
1083 * {@link Bundle#isSet(Enum, boolean)}).
1086 * the {@link Writer} to write into
1090 * @throws IOException
1091 * in case of IO error
1093 protected void writeValue(Writer writer
, E id
) throws IOException
{
1094 boolean set
= isSet(id
, false);
1095 writeValue(writer
, id
.name(), getString(id
), set
);
1099 * Write the given data to the config file, i.e., "MY_ID = my_curent_value"
1100 * followed by a new line.
1102 * Will prepend a # sign if the is is not set.
1105 * the {@link Writer} to write into
1111 * the value is set in this {@link Bundle}
1113 * @throws IOException
1114 * in case of IO error
1116 protected void writeValue(Writer writer
, String id
, String value
,
1117 boolean set
) throws IOException
{
1124 writer
.write(" = ");
1126 if (value
== null) {
1130 String
[] lines
= value
.replaceAll("\t", "\\\\\\t").split("\n");
1131 for (int i
= 0; i
< lines
.length
; i
++) {
1132 writer
.write(lines
[i
]);
1133 if (i
< lines
.length
- 1) {
1134 writer
.write("\\n\\");
1141 * Return the source file for this {@link Bundles} from the given path.
1144 * the path where the .properties files are
1146 * @return the source {@link File}
1148 protected File
getUpdateFile(String path
) {
1149 return new File(path
, keyType
.name() + ".properties");
1153 * Change the currently used bundle, and reset all changes.
1156 * the name of the bundle to load
1158 * the {@link Locale} to use
1159 * @param resetToDefault
1160 * reset to the default configuration (do not look into the
1161 * possible user configuration files, only take the original
1164 protected void setBundle(Enum
<?
> name
, Locale locale
, boolean resetToDefault
) {
1166 String dir
= Bundles
.getDirectory();
1167 String bname
= type
.getPackage().getName() + "." + name
.name();
1169 boolean found
= false;
1170 if (!resetToDefault
&& dir
!= null) {
1171 // Look into Bundles.getDirectory() for .properties files
1173 File file
= getPropertyFile(dir
, name
.name(), locale
);
1175 Reader reader
= new InputStreamReader(new FileInputStream(
1177 resetMap(new PropertyResourceBundle(reader
));
1180 } catch (IOException e
) {
1181 e
.printStackTrace();
1186 // Look into the package itself for resources
1188 resetMap(ResourceBundle
1189 .getBundle(bname
, locale
, type
.getClassLoader(),
1190 new FixedResourceBundleControl()));
1192 } catch (MissingResourceException e
) {
1193 } catch (Exception e
) {
1194 e
.printStackTrace();
1199 // We have no bundle for this Bundle
1200 System
.err
.println("No bundle found for: " + bname
);
1206 * Reset the backing map to the content of the given bundle, or with NULL
1207 * values if bundle is NULL.
1210 * the bundle to copy
1212 protected void resetMap(ResourceBundle bundle
) {
1214 if (bundle
!= null) {
1215 for (Field field
: type
.getDeclaredFields()) {
1217 Meta meta
= field
.getAnnotation(Meta
.class);
1219 E id
= Enum
.valueOf(type
, field
.getName());
1220 String value
= bundle
.getString(id
.name());
1221 this.map
.put(id
.name(),
1222 value
== null ?
null : value
.trim());
1224 } catch (MissingResourceException e
) {
1231 * Take a snapshot of the changes in memory in this {@link Bundle} made by
1232 * the "set" methods ( {@link Bundle#setString(Enum, String)}...) at the
1235 * @return a snapshot to use with {@link Bundle#restoreSnapshot(Object)}
1237 public Object
takeSnapshot() {
1238 return new HashMap
<String
, String
>(changeMap
);
1242 * Restore a snapshot taken with {@link Bundle}, or reset the current
1243 * changes if the snapshot is NULL.
1246 * the snapshot or NULL
1248 @SuppressWarnings("unchecked")
1249 public void restoreSnapshot(Object snap
) {
1253 if (snap
instanceof Map
) {
1254 changeMap
= (Map
<String
, String
>) snap
;
1256 throw new RuntimeException(
1257 "Restoring changes in a Bundle must be done on a changes snapshot, "
1258 + "or NULL to discard current changes");
1264 * Return the resource file that is closer to the {@link Locale}.
1267 * the directory to look into
1269 * the file base name (without <tt>.properties</tt>)
1271 * the {@link Locale}
1273 * @return the closest match or NULL if none
1275 private File
getPropertyFile(String dir
, String name
, Locale locale
) {
1276 List
<String
> locales
= new ArrayList
<String
>();
1277 if (locale
!= null) {
1278 String country
= locale
.getCountry() == null ?
"" : locale
1280 String language
= locale
.getLanguage() == null ?
"" : locale
1282 if (!language
.isEmpty() && !country
.isEmpty()) {
1283 locales
.add("_" + language
+ "-" + country
);
1285 if (!language
.isEmpty()) {
1286 locales
.add("_" + language
);
1293 for (String loc
: locales
) {
1294 file
= new File(dir
, name
+ loc
+ ".properties");
1295 if (file
.exists()) {