Reputation: 291
I would like to build ResourceBundles consisting of only a part of the key/value pairs in the properties files, thus saving a considerable number of files as compared to storing each of these parts or sections in an individual file. The sections are marked by a header line starting with "#" and separated from each other by an empty line. The sample properties file after the code below contains two sections:
I tried to achieve this in passing a customized ClassLoader, which reads only the desired section, in the getBundle(...) method. The CustomClassLoader works well in reducing the key definitions, but the ResourceBundle still contains all keys of the properties file.
import java.io.*;
import java.util.*;
public class ResourceReader {
public ResourceReader() {
Locale locale= Locale.getDefault();
ResourceBundle i18n= ResourceBundle.getBundle("ComponentBundle", locale,
new CustomClassLoader("#FileChooser"));
Enumeration<String> enu= i18n.getKeys();
System.out.println("Keys of ResourceBundle");
printEnumeration(enu);
}
public static void main(String args[]) {
new ResourceReader();
}
public void printEnumeration(Enumeration<String> enu) {
int i= 1;
while (enu.hasMoreElements()) {
System.out.println(i+".: "+enu.nextElement());
i++;
}
}
//////////////////////////////////////////////////////////////////////////////
public class CustomClassLoader extends ClassLoader {
String section;
public CustomClassLoader(String section) {
this.section= section;
}
@Override
public Class findClass(String name) throws ClassNotFoundException {
byte[] b = loadClassFromFile(name);
//System.out.writeBytes(b); // OK.
return defineClass(name, b, 0, b.length);
}
private byte[] loadClassFromFile(String fileName) {
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
fileName.replace('.', File.separatorChar) + ".properties");
byte[] buffer;
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
int nextValue = 0;
try {
while ( (nextValue = inputStream.read()) != -1 ) {
byteStream.write(nextValue);
}
} catch (IOException e) {
e.printStackTrace();
}
buffer = extractSection(byteStream.toString(), section);
return buffer;
}
private byte[] extractSection(String stream, String caption) {
final String LINE_SEP= System.getProperty("line.separator", "\n");
String[] lines= stream.split(LINE_SEP);
// Detect first and last line (exclusive) of section.
int iEnd= 0, iStart= -1;
for (int i=0; i<lines.length; i++) {
lines[i]= lines[i].trim();
if (iStart==-1) {
if (!lines[i].equals(caption)) continue;
iStart= i+1;
i++;
}
else if (lines[i].isEmpty()) {
iEnd= i;
break;
}
}
if (iEnd==0) iEnd= lines.length+1;
StringBuilder sb= new StringBuilder();
for (int i=iStart; i<iEnd; i++)
sb.append(lines[i]+LINE_SEP);
return sb.toString().getBytes();
}
}
}
//////////////////////////////////////////////////////////////////////////////
/* // File ComponentBundle.properties
#FileChooser
acceptAllFileFilterText= All files (*.*)
cancelButtonText= Cancel
cancelButtonToolTipText= Cancel
#OptionPane
Cancel= Cancel
Input= Input
Message= Message
No= No
// End of ComponentBundle.properties
*/
Upvotes: 0
Views: 710
Reputation: 291
If one absolutely wants to work with a subset of a ResourceBundle, this could be a way - although not very elegant as the complete bundle needs to be read and filtered.
int componentFlag= ...;
final int FILE_CHOOSER= 1, OPTION_PANE= 2;
ResourceBundle i18n= ResourceBundle.getBundle("ComponentBundle", locale);
String prefix;
Set<String> set= i18n.keySet();
if ((componentFlag&FILE_CHOOSER)>0)
prefix= "FileChooser.";
else if ((componentFlag&OPTION_PANE)>0)
prefix= "OptionPane.";
set.stream().filter(s -> s.startsWith(prefix))
.forEach(s -> UIManager.put(s, i18n.getString(s)));
Upvotes: 1
Reputation: 15086
You won't get your restricted resource bundles this way as you have over-ridden findClass
to return the bytes of a Properties file as a Class. To understand what is happening, add this code:
public URL getResource(String name)
{
var url = super.getResource(name);
System.out.println("getResource "+name+" -> "+url);
return url;
}
public Class findClass(String name) throws ClassNotFoundException {
System.out.println("findClass "+name);
...
You can then see what inner workings of ResourceBundle
does, which finds ALL keys because it is loading the contents of a file url - and your code is not used:
findClass ComponentBundle
getResource ComponentBundle.properties -> file:/C:/some/path/to/ComponentBundle.properties
findClass ComponentBundle_en
getResource ComponentBundle_en.properties -> null
findClass ComponentBundle_en_GB
getResource ComponentBundle_en_GB.properties -> null
It would be possible to get your bundler to work if you override getResource(String name)
and make that generate a file appropriate to the subset of the keys, passing back the URL to the subset file.
Its seems a lot of work to do when you could just define one resource bundle file for all your applications, or a resource bundle file per sub-component.
Upvotes: 1