The Java tutorials recommend using the Preferences API over Properties files.
Properties files and ResourceBundles are the recommended way to handle Internalization requirements in applications.
I am considering using both for a desktop application that will display preferences in a locale specific way.
Can anyone point out problems with this approach?
Maybe I should just use Properties files period?
I am considering using both for a desktop application that will display preferences in a locale specific way.
OK, so what you want is translated configuration file in form of:
some_translated_key=some_value
Well, unless you want to support MUI at some point it should not be a big deal. However, if you do, so that different users on the same computer could use different languages, or user might be able to switch language, you would have troubles in matching key to a property. You would have to scan all translations while reading the key, and you would surely end up with multiple entries for the same key. How to resolve that? Well, that's a good question.
From my experience, configuration files should be language-independent (neutral culture) and should never be edited by hand (that is translating keys doesn't really matter).
I thought there could be a problem with character encoding, but following code snippet works without an issue (files are UTF-8 encoded):
public class Main {
private static final String FILE_NAME = "i18ned.properties";
private File propertiesFile;
private Properties properties;
public Main() {
properties = new Properties();
propertiesFile = new File(FILE_NAME);
if (propertiesFile.exists()) {
try {
properties.load(new BufferedReader(new FileReader(
propertiesFile)));
} catch (FileNotFoundException e) {
// not likely, but should be logged either way
} catch (IOException e) {
// logger should be used instead
e.printStackTrace();
}
}
}
public void saveProperties() {
try {
properties
.store(new BufferedWriter(new FileWriter(propertiesFile)), "");
} catch (IOException e) {
// oops, use logger instead
e.printStackTrace();
}
}
public static void main(String[] args) {
Main main = new Main();
main.storeSome();
main.readSome();
}
private void readSome() {
String highAsciiKey = "żółć";
String value = properties.getProperty(highAsciiKey);
System.out.println(value);
}
private void storeSome() {
String highAsciiKey = "żółć";
String highAsciiValue = "łąkę";
properties.setProperty(highAsciiKey, highAsciiValue);
saveProperties();
}
}
Using resource bundle for localizing applications is the standard way in java. The problems of this way are:
there is no compile time check of number and type of parameters required by resource.
It is hard to hold files clean, e.g. there is no mechanism the helps to remove unused strings
It is hard to make all texts translated to all supported languages.
etc....
The probably better internationalization mechanism is suggested by Google in their GWT. They generate class with method per string.
For example if you have text Hello, {0} they will generate method
String hello(String name);
So, you cannot pass neither 0 nor 2 arguments to this method. Only one.
This partially solves the second problem also. It is easier to see if method is not used in whole project. It does not solve the 3rd problem anyway.
Related
I have declared a string in my strings.xml file , and using it in my activity as R.string.compose_title. (setting it as title i.e. setTitle(R.id.compose_title)). Now in some case I want to edit the string and then use it to set the title . How can I do this ?
P.S. I need to change value of a single string only , So declaring a new strings.xml for each case(which are variable depending upon the user) using localization seems to be a lil inefficient .
One thing what you have to understand here is that, when you provide a data as a Resource, it can't be modified during run time. For example, the drawables what you have in your drawable folder can't be modified at run time. To be precise, the "res" folder can't be modified programatically.
This applies to Strings.xml also, i.e "Values" folder. If at all you want a String which has to be modified at runtime, create a separate class and have your strings placed in this Class and access during run time. This is the best solution what I have found.
example howto:
how? by changing one variable reference to other reference
usage:
setRColor(pl.mylib.R.class,"endColor",pl.myapp.R.color.startColor);
// override app_name in lib R class
setRString(pl.mylib.R.class,"app_name",pl.myapp.R.string.app_name);
base methods:
public static void setRColor(Class rClass, String rFieldName, Object newValue) {
setR(rClass, "color", rFieldName, newValue);
}
public static void setRString(Class rClass, String rFieldName, Object newValue) {
setR(rClass, "string", rFieldName, newValue);
}
// AsciiStrings.STRING_DOLAR = "$";
public static void setR(Class rClass, String innerClassName, String rFieldName, Object newValue) {
setStatic(rClass.getName() + AsciiStrings.STRING_DOLAR + innerClassName, rFieldName, newValue);
}
helper methods :
public static boolean setStatic(String aClassName, String staticFieldName, Object toSet) {
try {
return setStatic(Class.forName(aClassName), staticFieldName, toSet);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return false;
}
}
public static boolean setStatic(Class<?> aClass, String staticFieldName, Object toSet) {
try {
Field declaredField = aClass.getDeclaredField(staticFieldName);
declaredField.setAccessible(true);
declaredField.set(null, toSet);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
#bradenV2 My app is supporting many languages , so I wanted to take a
string from my strings.xml that's currently in use and change that ,
and then use that one – atuljangra Mar 12 '12 at 22:04
ps the above solution is good for example when u want to inject some data in already compiled lib/jar. But if u want localize strings just make folder under res per LANG CODE like values-CC where cc is lang code (values-de,values-cs) etc
then u have 2 choices:
"build in" system dependent language selection - based on device selected lang
via create resources for configuration - you decide which lang show
like this:
configuration = new Configuration(resources.getConfiguration());
configuration.setLocale(targetLocale);
String localized = Context.createConfigurationContext(configuration)
.getResources()
.getString(resourceId);
I don't think you can programmatically customize the R class as it is built by ADT automatically.
I had a situation like this, where one of my strings.xml values had some dynamic piece of it. I set up the strings.xml with a "replacement text" (something like %%REPLACEMENT_EMAIL%%), and when I wanted to use that string programatically, I retrieved the string value of the resource, and replaced instances of that replacement text with the dynamic value (e.g. input by the user).
To be honest, my app has not been localized yet, but I'm still attempting to follow best practices w.r.t. not hardcoding any strings.
Use SharedPreferences instead of a Java class. It will give you more versatility if you decide to take values from the outside (web). Filling Java class in runtime can be useless offline. In case of SharedPreferences you have to ensure they are loaded only once, during app's first start, and then updated only by manual request, as previous import will be used.
myActivity.getSharedPreferences("com.example.imported",0)
.edit()
.putString("The news",getTheNews())
.apply();
Maybe you want to "modify" the string.xml so when it is required by the activity again it uses the new value, for example to keep a new dynamic title after screen rotation.
First, you can't modify the resource. It's already compiled. You can't modify the R class (what for?) all it's atributes are "final".
So, for the example above you can use onSaveInstanceState() and onRestoreInstanceState() for those properties you wanna keep on display.
According to my knowledge, you can't change resource value(R class value) while app running. why don't try to store on shared preference? I recommend you to use shared preference
I used below method to get the key-value pairs from the API and storing it in HashMap globally. If the key value is not found in HashMap then I will search that key in strings.xml file. It will achieve the purpose of dynamically changing the value of key.
public String getAppropriateLangText(String key) {
String value = "";
try {
HashMap<String, String> HashMapLanguageData HashMapLanguageData = gv.getHashMapLanguageData();
value = HashMapLanguageData.get(key);//Fetching the value of key from API
if (value == null || value.length() == 0) { //If Key value not found, search in strings.xml file
String packageName = getPackageName();
int resId = getResources().getIdentifier(key, "string", packageName);
value = getString(resId);
}
} catch (Exception e) {
value = "";
}
return value;
}
Im working on this assignment, in which im using a command line interface. Im using a simple switch statement to create the controls to this command line interface, however when I added internationalisation support as part of my assignment, my switch statement has broken due to the variables within needing to be constant. How do I get around this?
public class Editor {
private boolean running = true;
public static ArrayList<String> command = new ArrayList<String>();
Locale enLocale = new Locale("en", "GB");
ResourceBundle messages = ResourceBundle.getBundle("BaseBundle", enLocale);
String open = messages.getString("open");
String saveas = messages.getString("saveas");
String put = messages.getString("put");
String rot90 = messages.getString("rot90");
String mono = messages.getString("mono");
String help = messages.getString("help");
String quit = messages.getString("quit");
String look = messages.getString("look");
String undo = messages.getString("undo");
private String get = messages.getString("get");
Parser parser = new Parser();
public Editor() {
}
public void run() {
System.out.println("fotoshop");
while (running) {
command = parser.readInput();
interpret(command);
}
System.out.println("fotoshop exiting");
}
public void interpret(ArrayList<String> command) {
switch (command.get(0)) {
case open: OpenCommand.execute();
case saveas: SaveCommand.execute();
case put: PutCommand.execute();
case get: GetCommand.execute();
case rot90: ApplyRot90.execute();
case mono: ApplyMono.execute();
case help: HelpCommand.execute();
case quit: running = false;
case look: LookCommand.execute();
case undo;
}
}
}
I'm not very familiar with internationalization in Java, but I think the general pattern you should be using is something like this:
Map<String, String> i18Map = new HashMap<>();
map.put("open", "open"); // English
map.put("abierto", "open"); // Spanish
map.put("ouvrir", "open"); // French
// other words/languages
In other words, maintain some sort of map which can map a command, in any language you want to support, to a command English language command. Then, refactor your switch statement to switch on those English language constants instead of variables:
String input = parser.readInput();
String command = i18Map.get(input);
switch (command.get(0)) {
case "open": OpenCommand.execute();
case "saveas": SaveCommand.execute();
case "put": PutCommand.execute();
case "get": GetCommand.execute();
// etc.
}
Use a if else statement
Example
String com = command.get(0);
if (com.equals(open)) {
OpenCommand.execute()
} else if (com.equals(saveas)) {
SaveCommand.execute();
} ...
Have you looked at the following trick to implement dynamic switch?
It might be an overkill but it is worth having a look.
You can define it with String instead of Integer to map it to the different actions in the different languages your application should support.
java-tip-3-how-to-implement-dynamic-switch
class Switcher {
private Map<Integer, Command> caseCommands;
private Command defaultCommand;
private Command getCaseCommandByCaseId(Integer caseId) {
if (caseCommands.containsKey(caseId)) {
return caseCommands.get(caseId);
} else {
return defaultCommand;
}
}
public Switcher() {
caseCommands = new HashMap<Integer, Command>();
setDefaultCaseCommand(new DoNothingCommand());
}
public void addCaseCommand(Integer caseId, Command caseCommand) {
caseCommands.put(caseId, caseCommand);
}
public void setDefaultCaseCommand(Command defaultCommand) {
if (defaultCommand != null) {
this.defaultCommand = defaultCommand;
}
}
public void on(Integer caseId) {
Command command = getCaseCommandByCaseId(caseId);
command.execute();
}
}
Your Main class:
public class Main {
public static void main(String[] args) {
Switcher switcher = new Switcher();
switcher.addCaseCommand(1, new Command() {
#Override
public void execute() {
System.out.println("Command on {id: 1}");
}
});
switcher.addCaseCommand(2, new Command() {
#Override
public void execute() {
System.out.println("Command on {id: 2}");
}
});
switcher.addCaseCommand(3, new Command() {
#Override
public void execute() {
System.out.println("Command on {id: 3}");
}
});
switcher.setDefaultCaseCommand(new Command() {
#Override
public void execute() {
System.out.println("Command on {default}");
}
});
for (int i = 1; i <= 4; i++) {
switcher.on(i);
}
}
}
// output
Command on {id: 1}
Command on {id: 2}
Command on {id: 3}
Command on {default}
Or even better to avoid having all the messages directly hardcoded in the hashmap, and by consequence having to recompile change the code when adding new language support
Internationalizing the Sample Program
If you look at the internationalized source code, you'll notice that
the hardcoded English messages have been removed. Because the messages
are no longer hardcoded and because the language code is specified at
run time, the same executable can be distributed worldwide. No
recompilation is required for localization. The program has been
internationalized.
You may be wondering what happened to the text of the messages or what
the language and country codes mean. Don't worry. You'll learn about
these concepts as you step through the process of internationalizing
the sample program.
1. Create the Properties Files
A properties file stores information about the characteristics of a
program or environment. A properties file is in plain-text format. You
can create the file with just about any text editor.
In the example the properties files store the translatable text of the
messages to be displayed. Before the program was internationalized,
the English version of this text was hardcoded in the
System.out.println statements. The default properties file, which is
called MessagesBundle.properties, contains the following lines:
greetings = Hello
farewell = Goodbye
inquiry = How are you? Now that
the messages are in a properties file, they can be translated into
various languages. No changes to the source code are required. The
French translator has created a properties file called
MessagesBundle_fr_FR.properties, which contains these lines:
greetings = Bonjour.
farewell = Au revoir.
inquiry = Comment
allez-vous? Notice that the values to the right side of the equal sign
have been translated but that the keys on the left side have not been
changed. These keys must not change, because they will be referenced
when your program fetches the translated text.
The name of the properties file is important. For example, the name of
the MessagesBundle_fr_FR.properties file contains the fr language code
and the FR country code. These codes are also used when creating a
Locale object.
2. Define the Locale
The Locale object identifies a particular language and country. The
following statement defines a Locale for which the language is English
and the country is the United States:
aLocale = new Locale("en","US"); The next example creates Locale
objects for the French language in Canada and in France:
caLocale = new Locale("fr","CA"); frLocale = new Locale("fr","FR");
The program is flexible. Instead of using hardcoded language and
country codes, the program gets them from the command line at run
time:
String language = new String(args[0]);
String country = new String(args[1]);
currentLocale = new Locale(language, country); Locale
objects are only identifiers. After defining a Locale, you pass it to
other objects that perform useful tasks, such as formatting dates and
numbers. These objects are locale-sensitive because their behavior
varies according to Locale. A ResourceBundle is an example of a
locale-sensitive object.
3. Create a ResourceBundle
ResourceBundle objects contain locale-specific objects. You use
ResourceBundle objects to isolate locale-sensitive data, such as
translatable text. In the sample program the ResourceBundle is backed
by the properties files that contain the message text we want to
display.
The ResourceBundle is created as follows:
messages = ResourceBundle.getBundle("MessagesBundle", currentLocale);
The arguments passed to the getBundle method identify which properties
file will be accessed. The first argument, MessagesBundle, refers to
this family of properties files:
MessagesBundle_en_US.properties MessagesBundle_fr_FR.properties
MessagesBundle_de_DE.properties The Locale, which is the second
argument of getBundle, specifies which of the MessagesBundle files is
chosen. When the Locale was created, the language code and the country
code were passed to its constructor. Note that the language and
country codes follow MessagesBundle in the names of the properties
files.
Now all you have to do is get the translated messages from the
ResourceBundle.
4. Fetch the Text from the ResourceBundle
The properties files contain key-value pairs. The values consist of
the translated text that the program will display. You specify the
keys when fetching the translated messages from the ResourceBundle
with the getString method. For example, to retrieve the message
identified by the greetings key, you invoke getString as follows:
String msg1 = messages.getString("greetings"); The sample program uses
the key greetings because it reflects the content of the message, but
it could have used another String, such as s1 or msg1. Just remember
that the key is hardcoded in the program and it must be present in the
properties files. If your translators accidentally modify the keys in
the properties files, getString won't be able to find the messages.
Conclusion
That's it. As you can see, internationalizing a program isn't too
difficult. It requires some planning and a little extra coding, but
the benefits are enormous. To provide you with an overview of the
internationalization process, the sample program in this lesson was
intentionally kept simple. As you read the lessons that follow, you'll
learn about the more advanced internationalization features of the
Java programming language.
Source official doc: https://docs.oracle.com/javase/tutorial/i18n/intro/steps.html
One way to do this is with a Map where the keys are the internationalised strings, and the values are the commands that you want executed. So assuming that all the various Command classes (OpenCommand, SaveCommand and so on) have a common subtype Command where the execute method is declared, you could prime the map like this.
Map<String,Command> commandsByInternationalisedString = new HashMap<String,Command>();
Locale enLocale = new Locale("en", "GB");
ResourceBundle messages = ResourceBundle.getBundle("BaseBundle", enLocale);
commandsByInternationalisedString.add(messages.getString("open"), new OpenCommand());
commandsByInternationalisedString.add(messages.getString("saveas"), new SaveAsCommand());
commandsByInternationalisedString.add(messages.getString("put"), new PutCommand());
So now you have a map with a Command object corresponding to any command the user might enter. To use it, you could do this.
public void interpret(ArrayList<String> command) {
Command toExecute = commandsByInternationalisedString.get(command.get(0));
if (toExecute != null ) {
toExecute.execute();
} else {
System.out.println("That's not a valid command");
}
}
Working on an android app which gathers data from the Open Weather API as a JSON. However the JSON does not always contain the same keys (ie. sometimes cloud data or a weather description is included, sometimes it isn't).
Right now my code looks like (with some extra getters/setters I didn't include here):
public class WeatherDescrip {
private String weather;
private String weather_Desc;
private String icon;
public WeatherDescrip(JSONObject weatherObj) {
try {
weather = weatherObj.getString("main");
} catch (JSONException e) {
weather = null;
e.printStackTrace();
}
try {
weather_Desc = weatherObj.getString("description");
} catch (JSONException e) {
weather_Desc = null;
e.printStackTrace();
}
try {
icon = weatherObj.getString("icon");
} catch (JSONException e) {
icon = null;
e.printStackTrace();
}
}
}
Basically if the JSON I get from the API call doesn't have the necessary key I let the program throw an exception, which will usually happen with at least one piece of data each time the app is run (there is more done like this).
If anyone could please let me know whether this is an acceptable way to code, and possibly how to better implement this I would much appreciate it.
If you haven't noticed I'm also a total noob, sorry in advance if this is a terrible way of doing this.
Many Thanks
This is generally not the correct forum for asking opinions, as you're asking for subjective opinions, there's technically no way to gauge a 'correct' answer, although you're free to select whatever answer you choose, if any ;-)
But in the nature of good will, I'll give you a few of my opinions.
Firstly, Exceptions are for just that, exceptions. If you have a scenario where you are in control of the code, and are aware of a potential for something not to occur in an 'ideal' way (e.g. like this, you're receiving dodgy data), then code for it, i.e.
if (data.contains("somethingOfInterest")) {
consume(data);
} else {
getDataFromSomewhereElse();
}
Rather than throw an exception, and force your program to handle it somewhere else (or not). Here's some additional information on why it's not a good idea to use exceptions for control flow.
Also, and this is advice from personal experience; in most scenarios, it's a good idea to do as little as makes sense within an Object's constructor, as it's more ugly to recover if exceptions do occur inside a constructor's method body. Instead, it may be better to encapsulate the logic you have there in some other factory-esque class or method, passing only the gathered data to the constructor. Something like:
public class WeatherDescrip {
private String weather;
private String weather_Desc;
private String icon;
public WeatherDescrip(String weather, String weather_Desc, String icon) {
this.weather = weather;
this.weather_Desc = weather_Desc;
this.icon = icon;
}
}
...
public static WeatherDescrip createWeatherDescrip(JSONObject weatherObj) {
if (!weatherObj.containsKey("main")
|| !weatherObj.containsKey("description")
|| !weatherObj.containsKey("icon")) {
throw SomeNewMeaningfulException("That I understand and can explicitly handle");
or....
return getMyDataFromSomeWhereElse();
}
return new WeatherDescrip(
weatherObj.getString("main"),
weatherObj.getString("description"),
weatherObj.getString("icon")
);
}
I hope this helps.
It's acceptable to throw exceptions whenever you decide. You just need to play how you want to handle it.
Is it acceptable to crash the program and boot your user back to the home screen? Absolutely not. Ever
Just read your data and handle the exceptions gracefully - no icon? Display a default. No data? Tell the user there is a problem right now so they aren't misled by the old data being displayed.
An alternate to avoid the majority of exceptions is to use GSON and Retrofit (I've linked a useful set of tutorials, not the home of GSON or Retrofit). With GSON you can create a model object, automatically map the data and then on your getters always return a value even if the JSON was incomplete
Example:
class MyObj {
#SerializedName("main")
private String weather;
public String getWeather() {
String weatherResult = weather;
if (weatherResult == null || "".equals(weatherResult) {
weatherResult = getString(R.strings.weather_unavailable);
}
return weatherResult;
}
}
Throwing an exception is usually reserved for when an error occurs, rather than having it it being an expected result of running your code, since there is overhead in throwing an exception which can make your program execute (slightly) slower.
Realistically, it can be used whenever you like, however you like, but I might instead suggest using has() to check if the key exists before trying to access it. It's a more efficient way of achieving the same result, without having to throw or catch an exception.
if(weatherObj.has('description')) {
weather_Desc = weatherObj.getString("description");
} else {
weather_Desc = null;
}
I am facing a problem regarding readability and cross-platform issues.
We are generating our UI on the fly, using certain classes and subclasses. For an example, I will use basic elements (not UI ones), but I think the "problem" should be obvious.
I am asking IF there is any configuration switch in Simple (http://simple.sourceforge.net/home.php) to achieve the desired result. As said, its about readability for another platform, if I would only have to target JAVA, I would not care about the look and feel of the XML.
So, in my example, I serialize a simple class, the result is:
<GuiElementExamples>
<LastCreated>2012-04-15 16:48:59.813 CEST</LastCreated>
<NonGuiObject>
<objectBase class="objects.single.simple.StringObject" _value="">
<_readonly>false</_readonly>
</objectBase>
<objectBase class="objects.single.simple.StringProperty">
<_items>
<object class="objects.single.simple.StringObject" _value="Label">
<_readonly>true</_readonly>
</object>
<object class="objects.single.simple.StringObject" _value="">
<_readonly>false</_readonly>
</object>
</_items>
<_readonly>false</_readonly>
</objectBase>
</NonGuiObject>
</GuiElementExamples>
What I would love to have (I will create it by hand as an example), is this:
<GuiElementExamples>
<LastCreated>2012-04-15 16:48:59.813 CEST</LastCreated>
<NonGuiObject>
<StringObject _value="">
<_readonly>false</_readonly>
</StringObject>
<StringProperty>
<_items>
<StringObject _value="Label">
<_readonly>true</_readonly>
</StringObject>
<StringObject _value="">
<_readonly>false</_readonly>
</StringObject>
</_items>
<_readonly>false</_readonly>
</StringProperty>
</NonGuiObject>
</GuiElementExamples>
I KNOW there will be NO clashes in class names, and one option I could use is a simple search and replace script, but maybe there is an option to configure "simple" in a way to export the stuff as shown above.
I am aware that there would be no way to DESERIALIZE stuff in the format above, without running e.g. the mentioned script before, because without the fully qualified class name, simple can't know which object to create..
Thanks for any help or workaround ideas,
Chris
Like always, after writing it, and going back to the docs, I found it :-)
It is the last entry on the tutorial page, in my example this does the trick:
EDIT This does not work with more then one element, dont ask me why EDIT
#ElementListUnion({#ElementList(entry = "StringObject", type = StringObject.class)})
private ArrayList<T> _items = new ArrayList<T>();
The solution I used is a little bit more complicated, but get the job done. I will post the source here to point you in the right direction (ObjectBase is the base class of all my objects I want to "rename" the way described above):
public class FormatVisitor implements Visitor
{
private static ILogger _logger = LogManager.GetLogger(FormatVisitor.class);
#Override
public void read(Type type, NodeMap<InputNode> strings) throws Exception
{
}
public void write(Type type, NodeMap<OutputNode> node)
{
OutputNode element = node.getNode();
Class javaType = type.getType();
if (ObjectBase.class.isAssignableFrom(javaType))
{
_logger.Verbose("Old name was " + element.getName());
element.setName(javaType.getSimpleName());
_logger.Verbose("Changing name to " + javaType.getSimpleName());
}
try
{
String className = element.getAttributes().get("class").getValue();
Class localClass = Class.forName(className);
boolean shouldShortenName = ObjectBase.class.isAssignableFrom(localClass);
if (shouldShortenName)
{
element.getAttributes().remove("class");
element.setName(localClass.getSimpleName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
if (ObjectBase.class.isAssignableFrom(javaType))
{
element.setName(type.getType().getSimpleName());
}
}
}
I am receiving a string from user which should be used as a location to save content to a file. This string should contain enough information, like a directory + file name.
My question is, how can I check whether the provided string is a valid path to save content to a file (at least in theory)?
It does not matter whether directories are created or not, or whether one has proper access to the location itself. I am only interested in checking the structure of the provided string.
How should I proceed? I was thinking about creating a File object, then extracting its URI. Is there any better way?
You can use File.getCanonicalPath() to validate according the current OS rules.
import java.io.File;
import java.io.IOException;
public class FileUtils {
public static boolean isFilenameValid(String file) {
File f = new File(file);
try {
f.getCanonicalPath();
return true;
}
catch (IOException e) {
return false;
}
}
public static void main(String args[]) throws Exception {
// true
System.out.println(FileUtils.isFilenameValid("well.txt"));
System.out.println(FileUtils.isFilenameValid("well well.txt"));
System.out.println(FileUtils.isFilenameValid(""));
//false
System.out.println(FileUtils.isFilenameValid("test.T*T"));
System.out.println(FileUtils.isFilenameValid("test|.TXT"));
System.out.println(FileUtils.isFilenameValid("te?st.TXT"));
System.out.println(FileUtils.isFilenameValid("con.TXT")); // windows
System.out.println(FileUtils.isFilenameValid("prn.TXT")); // windows
}
}
Have you looked at Apache Commons IO? This library includes various things for handling path information which may help e.g. FilenameUtils.getPath(String filename) which returns the path from a full filename.
Easiest: try to save, listen for exceptions.
The only time I'd do something more complicated would be if the writing was to be deferred, and you want to give the user his feedback now.
Here is what I have so far:
private static boolean checkLocation(String toCheck) {
// If null, we necessarily miss the directory section
if ( toCheck == null ) {
System.out.println("Missing directory section");
return false;
}
String retrName = new File(toCheck).toURI().toString();
// Are we dealing with a directory?
if ( retrName.charAt(retrName.length()-1) == '/') {
System.out.println("Missing file name");
return false;
}
return true;
}
This tells me whether I have a proper directory structure and whether I am pointing to a directory rather than a file. I do not need I/O access.
I have noticed that if I use the File.createNewFile() method on a location pointing explicitly to a directory (which does not exist yet), Java creates a file with no extension, which is plain wrong. Either it should create a directory or it should throw some kind of error.
Also, the File constructors tend to add the current directory if none is provided in the argument. It is not documented, but no real harm in my case.
If anyone has a better solution, I'll approve it.
EDIT
I have finally combined the above with the input from RealHowTo.