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
.UnsupportedEncodingException
;
12 import java
.io
.Writer
;
13 import java
.lang
.reflect
.Field
;
14 import java
.util
.ArrayList
;
15 import java
.util
.List
;
16 import java
.util
.Locale
;
17 import java
.util
.MissingResourceException
;
18 import java
.util
.PropertyResourceBundle
;
19 import java
.util
.ResourceBundle
;
21 import be
.nikiroo
.utils
.resources
.Bundles
;
22 import be
.nikiroo
.utils
.resources
.Meta
;
25 * This class encapsulate a {@link ResourceBundle} in UTF-8. It only allows to
26 * retrieve values associated to an enumeration, and allows some additional
32 * the enum to use to get values out of this class
34 public class Bundle
<E
extends Enum
<E
>> {
35 protected Class
<E
> type
;
36 protected Enum
<?
> name
;
37 private ResourceBundle map
;
40 * Create a new {@link Bundles} of the given name.
43 * a runtime instance of the class of E
46 * the name of the {@link Bundles}
48 protected Bundle(Class
<E
> type
, Enum
<?
> name
) {
51 setBundle(name
, Locale
.getDefault());
55 * Return the value associated to the given id as a {@link String}.
58 * the id of the value to get
60 * @return the associated value, or NULL if not found (not present in the
63 public String
getString(E id
) {
64 return getStringX(id
, "");
68 * Return the value associated to the given id as a {@link String} suffixed
69 * with the runtime value "_suffix" (that is, "_" and suffix).
72 * the id of the value to get
76 * @return the associated value, or NULL if not found (not present in the
79 public String
getStringX(E id
, String suffix
) {
80 String key
= id
.name()
81 + ((suffix
== null || suffix
.isEmpty()) ?
"" : "_"
82 + suffix
.toUpperCase());
84 if (containsKey(key
)) {
85 return getString(key
).trim();
92 * Return the value associated to the given id as a {@link Boolean}.
95 * the id of the value to get
97 * @return the associated value
99 public Boolean
getBoolean(E id
) {
100 String str
= getString(id
);
101 if (str
!= null && str
.length() > 0) {
102 if (str
.equalsIgnoreCase("true") || str
.equalsIgnoreCase("on")
103 || str
.equalsIgnoreCase("yes"))
105 if (str
.equalsIgnoreCase("false") || str
.equalsIgnoreCase("off")
106 || str
.equalsIgnoreCase("no"))
115 * Return the value associated to the given id as a {@link boolean}.
118 * the id of the value to get
120 * the default value when it is not present in the config file or
121 * if it is not a boolean value
123 * @return the associated value
125 public boolean getBoolean(E id
, boolean def
) {
126 Boolean b
= getBoolean(id
);
134 * Return the value associated to the given id as an {@link Integer}.
137 * the id of the value to get
139 * @return the associated value
141 public Integer
getInteger(E id
) {
143 return Integer
.parseInt(getString(id
));
144 } catch (Exception e
) {
151 * Return the value associated to the given id as a {@link int}.
154 * the id of the value to get
156 * the default value when it is not present in the config file or
157 * if it is not a int value
159 * @return the associated value
161 public int getInteger(E id
, int def
) {
162 Integer i
= getInteger(id
);
170 * Return the value associated to the given id as a {@link Character}.
173 * the id of the value to get
175 * @return the associated value
177 public char getChar(E id
) {
178 String s
= getString(id
).trim();
179 if (s
.length() > 0) {
187 * Create/update the .properties file. Will use the most likely candidate as
188 * base if the file does not already exists and this resource is
189 * translatable (for instance, "en_US" will use "en" as a base if the
190 * resource is a translation file).
193 * the path where the .properties files are
195 * @throws IOException
196 * in case of IO errors
198 public void updateFile(String path
) throws IOException
{
199 File file
= getUpdateFile(path
);
201 BufferedWriter writer
= new BufferedWriter(new OutputStreamWriter(
202 new FileOutputStream(file
), "UTF-8"));
208 for (Field field
: type
.getDeclaredFields()) {
209 Meta meta
= field
.getAnnotation(Meta
.class);
211 E id
= E
.valueOf(type
, field
.getName());
212 String info
= getMetaInfo(meta
);
219 writeValue(writer
, id
);
227 * Check if the internal map contains the given key.
230 * the key to check for
232 * @return true if it does
234 protected boolean containsKey(String key
) {
238 } catch (MissingResourceException e
) {
244 * Get the value for the given key if it exists in the internal map.
247 * the key to check for
249 * @return true if it does
251 protected String
getString(String key
) {
252 if (containsKey(key
)) {
254 // Note: it is also possible to fix the borked charset issue
255 // with a custom ResourceBundle#Control class, but this one,
256 // while a workaround, depend less upon the JRE classes, which
258 return new String(map
.getString(key
).getBytes("ISO-8859-1"),
260 } catch (UnsupportedEncodingException e
) {
261 // Those 2 encodings are always supported
270 * Return formated, display-able information from the {@link Meta} field
271 * given. Each line will always starts with a "#" character.
274 * the {@link Meta} field
276 * @return the information to display or NULL if none
278 protected String
getMetaInfo(Meta meta
) {
279 String what
= meta
.what();
280 String where
= meta
.where();
281 String format
= meta
.format();
282 String info
= meta
.info();
284 int opt
= what
.length() + where
.length() + format
.length();
285 if (opt
+ info
.length() == 0)
288 StringBuilder builder
= new StringBuilder();
289 builder
.append("# ");
293 if (what
.length() > 0) {
294 builder
.append("WHAT: " + what
);
295 if (where
.length() + format
.length() > 0)
296 builder
.append(", ");
299 if (where
.length() > 0) {
300 builder
.append("WHERE: " + where
);
301 if (format
.length() > 0)
302 builder
.append(", ");
305 if (format
.length() > 0) {
306 builder
.append("FORMAT: " + format
);
310 if (info
.length() > 0) {
311 builder
.append("\n# ");
315 builder
.append(info
);
317 return builder
.toString();
321 * The display name used in the <tt>.properties file</tt>.
325 protected String
getBundleDisplayName() {
326 return name
.toString();
330 * Write the header found in the configuration <tt>.properties</tt> file of
331 * this {@link Bundles}.
334 * the {@link Writer} to write the header in
336 * @throws IOException
337 * in case of IO error
339 protected void writeHeader(Writer writer
) throws IOException
{
340 writer
.write("# " + getBundleDisplayName() + "\n");
345 * Write the given id to the config file, i.e., "MY_ID = my_curent_value"
346 * followed by a new line
349 * the {@link Writer} to write into
353 * @throws IOException
354 * in case of IO error
356 protected void writeValue(Writer writer
, E id
) throws IOException
{
357 writeValue(writer
, id
.name(), getString(id
));
361 * Write the given data to the config file, i.e., "MY_ID = my_curent_value"
362 * followed by a new line
365 * the {@link Writer} to write into
371 * @throws IOException
372 * in case of IO error
374 protected void writeValue(Writer writer
, String id
, String value
)
383 String
[] lines
= value
.replaceAll("\\\t", "\\\\\\t").split("\n");
384 for (int i
= 0; i
< lines
.length
; i
++) {
385 writer
.write(lines
[i
]);
386 if (i
< lines
.length
- 1) {
387 writer
.write("\\n\\");
394 * Return the source file for this {@link Bundles} from the given path.
397 * the path where the .properties files are
399 * @return the source {@link File}
401 * @throws IOException
402 * in case of IO errors
404 protected File
getUpdateFile(String path
) {
405 return new File(path
, name
.name() + ".properties");
409 * Change the currently used bundle.
412 * the name of the bundle to load
414 * the {@link Locale} to use
416 protected void setBundle(Enum
<?
> name
, Locale locale
) {
418 String dir
= Bundles
.getDirectory();
422 File file
= getPropertyFile(dir
, name
.name(), locale
);
424 Reader reader
= new InputStreamReader(new FileInputStream(
426 map
= new PropertyResourceBundle(reader
);
428 } catch (IOException e
) {
434 map
= ResourceBundle
.getBundle(type
.getPackage().getName() + "."
435 + name
.name(), locale
);
440 * Return the resource file that is closer to the {@link Locale}.
443 * the dirctory to look into
445 * the file basename (without <tt>.properties</tt>)
449 * @return the closest match or NULL if none
451 private File
getPropertyFile(String dir
, String name
, Locale locale
) {
452 List
<String
> locales
= new ArrayList
<String
>();
453 if (locale
!= null) {
454 String country
= locale
.getCountry() == null ?
"" : locale
456 String language
= locale
.getLanguage() == null ?
"" : locale
458 if (!language
.isEmpty() && !country
.isEmpty()) {
459 locales
.add("_" + language
+ "-" + country
);
461 if (!language
.isEmpty()) {
462 locales
.add("_" + language
);
469 for (String loc
: locales
) {
470 file
= new File(dir
, name
+ loc
+ ".properties");