No. I mean for some subtype of 'Foo', possibly 'Foo' itself, to be the actual
type of the constructed instance, regardless of whether you specify the
'Stream' or 'Locale' or any other attribute.
Sorry, that's just a jumble of words to me. I don't mean that you are
being unclear but that I'm afraid I don't do very well when everything
is put in very general terms like 'Foo', especially if I'm not clear
on what kind of thing Foo is supposed to be. And the fact that we've
got all these threads going with umpteen different considerations
expressed, some of which I only half understand, is definitely
complicating things.... And now my mail reader is refusing to connect!
Arrrggghhhh!! Okay, switching to Google Groups.....
'Locale' or 'Stream' or any other attribute for which the factory is
responsible, the principles are the same.
What makes you think that the non-default 'Locale' setting has anything to do
with using a subtype? I showed use of a subtype in both cases.
If you are only constructing 'Foo' instances and never different implementors
of 'Foo', you probably don't need a factory class and should just use 'Foo'
constructors.
One of the main purposes of a factory class is to provide a hidden
implementation of the constructed type. The returned type in such cases is
nearly always an interface.
You never answered my question about why you wanted to use a static factory
method, let alone a factory class, in the first place. The question was
neither arbitrary nor insignificant.
Actually, I did answer it a few hours ago on one of the other threads.
I'm darned if I know which one at this point; there are so many
threads going now with so many subthreads that I am absolutely dizzy
keeping up wiht them and figuring out which ones I have
"answered" (affirmed that I understood or asked a followup or
clarifying question) and which ones I've missed.
Rather than tracking it down right now or risking paraphrasing my
remarks inaccurately and causing further confusion, it would be
helpful to _me_ to simply post LocalizationUils and/or
LocalizationUtils2 right now. They are quite brief and they would give
me some concrete reference points in asking questions. Basically, I
want to ask if I have done things correctly now and identify any
things that still need work. LocalizationUtils is satisfactory, I
should then be able to make equivalent changes on the other utility
classes.
I've replaced displayLocales(), which wrote to the console, with
writeLocales(PrintWriter) and writeLocales(PrintStream).
By the way, I have the JUnit tests doing all (or most?) of what
various people suggested. Those tests all give green checkmarks so I
_think_ I've proven that my functionality all works. With regards to
the writeXXX methods that write the Locales list, my test cases write
them to a file, then read them back from the file and parse them
sufficiently to verify that all 152 Locales are present and that
en_CA, en_US, fr_FR and de_DE are all there.
I'm just not sure if the overall design in right and I don't think I'm
understanding the right place to create the Stream or Writer that I
need.
Okay, let's just dive in. Here is LocalizationUtils2, with all the
imports, comments and javadocs stripped out for brevity:
===================================================================
public class LocalizationUtils2 {
static final String CLASS_NAME = "LocalizationUtils";
private final static String MSG_PREFIX =
"ca.maximal.common.utilities.Resources.LocalizationUtilsMsg";
public static final String HORIZONTAL_SEPARATOR = "\t";
StringUtils stringUtils = null;
private ResourceBundle locMsg = null;
private MessageFormat msgFmt = new MessageFormat("");
private Locale locale = null;
private LocalizationUtils2() {
locale = Locale.getDefault();
locMsg = getResources(locale, MSG_PREFIX);
msgFmt.setLocale(locale);
}
public static LocalizationUtils2 getInstance() {
return new LocalizationUtils2();
}
public String greeting() {
Object[] msgArgs = {};
msgFmt.applyPattern(locMsg.getString("msg012"));
return (msgFmt.format(msgArgs));
}
public SortedMap<String, Locale> getLocales() {
SortedMap<String, Locale> sortedLocales = new TreeMap<String,
Locale>();
for (Locale oneLocale : Locale.getAvailableLocales()) {
sortedLocales.put(oneLocale.getDisplayName(locale), oneLocale);
}
return sortedLocales;
}
public void writeLocales(PrintStream out) {
SortedMap<String, Locale> locales = getLocales();
int longestLocaleName = 0;
for (String oneLocale : locales.keySet()) {
if (oneLocale.length() > longestLocaleName) {
longestLocaleName = oneLocale.length();
}
}
stringUtils = StringUtils.getInstance();
for (String oneLocale : locales.keySet()) {
out.println(stringUtils.pad(oneLocale, ' ', 'T',
longestLocaleName) + HORIZONTAL_SEPARATOR +
locales.get(oneLocale));
}
out.flush();
}
public void writeLocales(PrintWriter out) {
SortedMap<String, Locale> locales = getLocales();
int longestLocaleName = 0;
for (String oneLocale : locales.keySet()) {
if (oneLocale.length() > longestLocaleName) {
longestLocaleName = oneLocale.length();
}
}
stringUtils = StringUtils.getInstance();
for (String oneLocale : locales.keySet()) {
out.println(stringUtils.pad(oneLocale, ' ', 'T', longestLocaleName) +
HORIZONTAL_SEPARATOR + locales.get(oneLocale));
}
out.flush();
}
public ResourceBundle getResources(Locale currentLocale, String
listBase) {
final String METHOD_NAME = "getResources()";
ResourceBundle locList = null;
try {
locList = ResourceBundle.getBundle(listBase, currentLocale);
} catch (MissingResourceException mr_excp) {
Object[] msgArgs = {CLASS_NAME, METHOD_NAME, listBase,
currentLocale.toString(), mr_excp};
msgFmt.applyPattern(locMsg.getString("msg011"));
throw new IllegalArgumentException(msgFmt.format(msgArgs));
}
return (locList);
}
}
===================================================================
I initially had two private constructors, the one you see here which
takes no Locale parameter, and a second one which did. I initially had
two getInstance() methods, one for each constructor, so one had no
parameter and one took Locale as its parameter. I also had a
getLocale() and setLocale(). This was the "everything INCLUDING the
kitchen sink" version and allowed people to invoke the class with the
default Locale or a specified one and then to change Locales anytime
they wanted. Some of the discussion here persuaded me that this was
too much so I stripped out the constructor and getInstance() that used
the Locale parameter and removed getLocale() and setLocale(). (And
yes, I have an open mind on this and could be persuaded to restore
them; I've kept the previous version of the class around just in case
I want to do this.)
I know now that the user will get messages according to his
user.language and user.country settings. (I created the frivolous
greeting() method and made ResourceBundles just to demonstrate this. I
made ResourceBundles for English, French and German containing a
single greeting (Merry Christmas) in each of those languages with the
key of msg012. I changed my user.language and user.country settings in
the JVM via the run profile in Eclipse and verified that, depending on
which values those settings have, the user will get the greeting in
that language.) Therefore, I am supporting users that speak various
languages and can easily add more. Users can have whichever of the
supported languages they want simply by setting user.language and
user.country. Users who want to switch on the fly from English to
German and then to French can't do that; they are out of luck. But the
ability to switch on the fly is probably overbuilding anyway. That's
my (tentative) executive decision on that
Getting expert reactions to this aspect of the class is my main issue
at this point.
The lesser issue is the whole business about where the Writer or
Stream used by writeLocales() should be done. At the moment, I create
the Writer or Stream in my JUnit test case. It seems to me that anyone
writing a class calling my utility class would expect to create the
Writer or Stream there, not that I would do it for them. But I'm
definitely willing to keep an open mind on that!
I suppose my biggest reservation about creating the Writer or Stream
in my class is the fear that it limits what the caller gets. For
instance, if I create a Stream that writes to a physical file but the
caller wants to redirect the output to the console, aren't I limiting
his options unnecessarily?
If I should create the Writer or Stream in LocalizationUtils2, what
characteristics should I give it?
Since it seems we've decided against using Properties as a model, can
someone suggest a better model that does things the way the suggester
(Daniel?) meant?
Lastly, I would be happy to post the test cases for the class here to
get people's comments if they'd like to see them. I don't expect that
they'd be controversial but there might well be things that I could
have done more efficiently. For instance, I test the writeLocales()
methods by sending the output to a flat file, then read them back and
parse them to be sure that the right number of Locales was written and
that four specific Locales were present. That code is a bit bulkier
than I've had to do in other tests so maybe there is a better way.....
Would it help or confuse things if I replicated this post on the OTHER
threads that are discussing these issues, particularly the Design
Questions for Static Factory Methods one?