Find place for dedicated application folder - java

I'm sorry if this title is badly named, I can't think of a better way to phrase it and so edits would be welcomed.
Most applications I've seen that require hard drive file storage create a folder in a suitable place depending on the operating system. On Windows these folders lie in \Users\[current user]\AppData\[etc], on Mac these folders lie in /Users/[current user]/Library/Application Support/[etc], Ubuntu has a similar thing that I can't think of right now.
I would like to know, how are these file paths consistently found in differing operating systems with different users, and is there, at least in java, a simple way to achieve this?
Thank you.

There should be, but there isn't. I even submitted a bug/RFE about it, but as far as I know, it was never accepted. Here's what I use:
public class ApplicationDirectories {
private static final Logger logger =
Logger.getLogger(ApplicationDirectories.class.getName());
private static final Path config;
private static final Path data;
private static final Path cache;
static {
String os = System.getProperty("os.name");
String home = System.getProperty("user.home");
if (os.contains("Mac")) {
config = Paths.get(home, "Library", "Application Support");
data = config;
cache = config;
} else if (os.contains("Windows")) {
String version = System.getProperty("os.version");
if (version.startsWith("5.")) {
config = getFromEnv("APPDATA", false,
Paths.get(home, "Application Data"));
data = config;
cache = Paths.get(home, "Local Settings", "Application Data");
} else {
config = getFromEnv("APPDATA", false,
Paths.get(home, "AppData", "Roaming"));
data = config;
cache = getFromEnv("LOCALAPPDATA", false,
Paths.get(home, "AppData", "Local"));
}
} else {
config = getFromEnv("XDG_CONFIG_HOME", true,
Paths.get(home, ".config"));
data = getFromEnv("XDG_DATA_HOME", true,
Paths.get(home, ".local", "share"));
cache = getFromEnv("XDG_CACHE_HOME", true,
Paths.get(home, ".cache"));
}
}
/** Prevents instantiation. */
private ApplicationDirectories() {
}
/**
* Retrieves a path from an environment variable, substituting a default
* if the value is absent or invalid.
*
* #param envVar name of environment variable to read
* #param mustBeAbsolute whether enviroment variable's value should be
* considered invalid if it's not an absolute path
* #param defaultPath default to use if environment variable is absent
* or invalid
*
* #return environment variable's value as a {#code Path},
* or {#code defaultPath}
*/
private static Path getFromEnv(String envVar,
boolean mustBeAbsolute,
Path defaultPath) {
Path dir;
String envDir = System.getenv(envVar);
if (envDir == null || envDir.isEmpty()) {
dir = defaultPath;
logger.log(Level.CONFIG,
envVar + " not defined in environment"
+ ", falling back on \"{0}\"", dir);
} else {
dir = Paths.get(envDir);
if (mustBeAbsolute && !dir.isAbsolute()) {
dir = defaultPath;
logger.log(Level.CONFIG,
envVar + " is not an absolute path"
+ ", falling back on \"{0}\"", dir);
}
}
return dir;
}
/**
* Returns directory where the native system expects an application
* to store configuration files for the current user. No attempt is made
* to create the directory, and no checks are done to see if it exists.
*
* #param appName name of application
*/
public static Path configDir(String appName)
{
return config.resolve(appName);
}
/**
* Returns directory where the native system expects an application
* to store implicit data files for the current user. No attempt is made
* to create the directory, and no checks are done to see if it exists.
*
* #param appName name of application
*/
public static Path dataDir(String appName)
{
return data.resolve(appName);
}
/**
* Returns directory where the native system expects an application
* to store cached data for the current user. No attempt is made
* to create the directory, and no checks are done to see if it exists.
*
* #param appName name of application
*/
public static Path cacheDir(String appName)
{
return cache.resolve(appName);
}
}
Some notes:
I'm not sure the code for older Windows versions is even necessary, as Java 8 doesn't run on Windows XP.
The XDG Directory Specification says “All paths set in these environment variables must be absolute. If an implementation encounters a relative path in any of these variables it should consider the path invalid and ignore it.”

The directory can be found with the method
static String defaultDirectory() {
String os = System.getProperty("os.name").toLowerCase();
if (OS.contains("win"))
return System.getenv("APPDATA");
else if (OS.contains("mac"))
return System.getProperty("user.home") + "/Library/Application Support";
else if (OS.contains("nux"))
return System.getProperty("user.home");
else
return System.getProperty("user.dir");
}
It is worth noting that on Linux any such folders should be hidden by beginning their name with .
(Answer found from users CodeBunny and Denis Tulskiy on this post)

Related

Quickly find if Java was launched from Windows cmd or Cygwin terminal

I have a Java application that will be used both from the Windows Command Prompt and the Cygwin terminal. The program uses and manipulates file paths. It we be very useful to have a sep variable that would be / when the program is launched from Cygwin but \\ when the program is launched from Windows.
Looking here, I'm not sure it will be possible, but I want to ask.
I will post a small, compilable app that shows the issue in a few minutes. For now, I'll just say that I want a set of functions that something like:
// in main
...
String sep = getSeparatorToUse();
...
// member functions
...
private boolean wasLaunchedFromWinCmd()
{
if (<something-here-that-knows-it-was-cmd-not-cygwin>)
return true;
return false;
}//endof: private boolean wasLaunchedFromWinCmd()
private String getSeparatorToUse()
{
if (wasLaunchedFromWinCmd)
return "\\"
return "/"
}//endof: private String getSeparatorToUse()
Thanks #Raphael_Moita. Those are very useful, and I will likely use them in the Linux version of the app that I will be using. #Luke_Lee, I feel dumb not having realized it. I think you two might have solved my problem while I was getting the compilable code ready. There's still one issue when the program run from a batch script - when it is fed a filename from a find command. I hope what I show will illustrate that.
Examples
All examples are as run from Cygwin.
Works: the way most volunteers use the code, just the filename that's in the same directory as the java code.
$ java FileSeparatorExample pic_4.jpg
Here, something will be done with the file,
C:\Users\bballdave025\Desktop\pic_4.jpg
Works: with relative filepaths and spaces in filenames/file paths
$ java FileSeparatorExample pretty\ pictures/pic\ 1.jpg
Here, something will be done with the file,
C:\Users\me\Desktop\pretty pictures/pic 1.jpg
$ java FileSeparatorExample ../pic_5.jpg
Here, something will be done with the file,
C:\Users\me\Desktop\../pic_5.jpg
DOESN'T WORK. Sometimes, the output of a find command will come with the complete filepath in Cygwin/UNIX format:
$ java FileSeparatorExample /cygdrive/c/David/example/pic.jpg
The file:
C:\Users\bballdave025\Desktop\/cygdrive/c/David/example/pic.jpg
doesn't exist
Compilable Code
I'm just cutting down from my original code, so I'm sorry if it seems too big.
/**********************************
* #file FileSeparatorExample.java
**********************************/
// Import statements
import java.io.File;
import java.io.IOException;
public class FileSeparatorExample
{
// Member variables
private static String sep;
public static void main(String[] args)
{
////****** DOESN'T WORK AS DESIRED ******////
sep = java.io.File.separator;
////** I want **////
// sep = getFileSeparator();
String imageToLoad = null;
boolean argumentExists = ( args != null && args.length != 0 );
if (argumentExists)
{
boolean thereIsExactlyOneArgument = ( args.length == 1 );
if (thereIsExactlyOneArgument)
{
imageToLoad = args[0];
}//endof: if (thereIsExactlyOneArgument)
else
{
// do some other stuff
}
}//endof: if (argumentExists)
String filenamePath = getFilenamePath(imageToLoad);
String filenameFile = getFilenameFile(imageToLoad);
imageToLoad = filenamePath + sep + filenameFile;
File f = new File(imageToLoad);
if (! f.exists())
{
System.err.println("The file:");
System.err.println(imageToLoad);
System.err.println("doesn\'t exist");
System.exit(1);
}//endof: if (! f.exists())
System.out.println("Here, something will be done with the file,");
System.out.println(imageToLoad);
}//endof: main
// member methods
/**
* Separates the filename arg into: full path to directory; bare filename
*/
private static String[] splitFilename(String imageToLoad)
{
String[] fileParts = new String[2];
int indexOfLastSep = imageToLoad.lastIndexOf(sep);
boolean fullFilenameHasSeparator = ( indexOfLastSep != -1 );
if (fullFilenameHasSeparator)
{
fileParts[0] = imageToLoad.substring(0, indexOfLastSep);
fileParts[1] = imageToLoad.substring(indexOfLastSep + 1);
}//endof: if (fullFilenameHasSeparator)
else
{
// Use the user's directory as the path
fileParts[0] = System.getProperty("user.dir");
fileParts[1] = imageToLoad;
}//endof: if/else (fullFilenameHasSeparator)
return fileParts;
}//endof: private static String[] splitFilename(String imageToLoad)
/**
* Gives the full path to the file's directory (from the filename arg)
* but not the bare filename
*/
private static String getFilenamePath(String imageToLoad)
{
String[] fileParts = splitFilename(imageToLoad);
return fileParts[0];
}//endof: private static String getFilenamePath(String imageToLoad)
/**
* Gives the bare filename (no path information)
*/
private static String getFilenameFile(String imageToLoad)
{
String[] fileParts = splitFilename(imageToLoad);
return fileParts[1];
}//endof: private static String getFilenamePath(String imageToLoad)
}//endof: public class FileSeparatorExample
You don't need to know which SO is under your Java. If your goal is to find the correct file separator to use, call this:
java.io.File.separator;
Anyway ... to find out which SO java is running over (not sure how cygwin is detected by this), try:
boolean isWindows = System.getProperty("os.name").startsWith("win");
Here is an answer I've come up with that almost answers my original question. It tries to determine the launcher of the Java code based on the filename argument. A big thanks to #Raphael_Moita and #Luke_Lee, who actually pretty much solved my problem. Their solutions didn't answer the original question, but that's partly because I didn't post the original question completely. As I said, this answer doesn't answer the original question completely. If someone knows the complete solution, please let me know.
My solution was a few methods. As they stand, they only work for my case - Cygwin on Windows. (What they do is tell you if the filename argument for the Java application was consistent with being launched from Windows cmd or not.) I plan on posting a more portable group of methods, i.e. other Operating Systems.
I'm sure there are issues. Please point them out to me.
// in main
...
sep = java.io.File.separator; // Thanks #Luke_Lee
if (args != null && args.length != 0)
sep = getSeparatorToUse(args[0]);
...
// member functions
...
private boolean wasLaunchedFromWinCmd(String firstArg)
{
boolean isWindows = System.getProperty("os.name").startsWith("win");
if (! isWindows) return false; // Thanks #Raphael_Moita
else
{
String launchDir = System.getProperty("user.dir");
String rootOfLaunchDir = getRoot(launchDir);
// This will come back with something like "C:\" or "P:\"
String rootOfArgument = getRoot(firstArg);
if (rootOfArgument.equals("/"))
{
String cygwinBase = "/cygdrive/";
char letterOfRoot = rootOfLaunchDir.charAt(0);
// For, e.g., "/Users/me/Desktop/pic_314.jpg"
if (firstArg.startsWith(cygwinBase))
{
int charsToCut = cygwinBase.length();
letterOfRoot = firstArg.substring(charsToCut,
charsToCut + 1);
}//endof: if (firstArg.startsWith(cygwinBase))
System.out.println("The root directory of your argument will be:");
System.out.println(Character.toUpperCase(letterOfRoot) + ":\\");
System.out.println("In Cygwin, that will be:");
System.out.println(cygwinBase +
Character.toLowerCase(letterOfRoot) + "/");
return false;
// Not always correct, e.g. if someone in Cygwin uses
// $ java FileSeparatorExample "C:\pic_137.jpg"
}//endof: if (rootOfArgument.equals("/"))
return true;
}//endof: if/else (! isWindows)
}//endof: private boolean wasLaunchedFromCmd()
private String getRoot(String fileOrDir)
{
File file = new File(fileOrDir).getAbsoluteFile();
File root = file.getParentFile();
while (root.getParentFile() != null)
root = root.getParentFile();
return root.toString();
}//endof: private String getRoot();
private String getSeparatorToUse(String firstArg)
{
if (wasLaunchedFromWinCmd(firstArg))
return "\\"
return "/"
}//endof: private String getSeparatorToUse(String firstArg)
Parts of this solution are due to #Raphael_Moita and #Luke_Lee, but I also need to reference this SO post. This last one helped with my specific situation, where the files are not all hosted on the C:\ drive.
Note
I won't be accepting mine as the correct solution, because it doesn't answer my original question. I hope it might help someone with answering the original question.

How to save txt on custom path

I have a code that saves a textfile, but the path of that textfile and the name are hardcoded.
How can i make that the user select his/her own path and the name of the file?
int[][] sudokuNumbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
try (
PrintStream output = new PrintStream(new File("C:\\Users\\David\\Desktop\\Proyecto4.3\\output.txt"));) {
for (int i = 0; i < sudokuNumbers.length; i++) {
String s= "";
for (int j = 0; j < sudokuNumbers[i].length; j++) {
s+= "|" + sudokuNumbers[i][j] + "|";
}
output.println(s);
}
output.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
The task is relatively easy to carry out however you will need a couple methods to assist you. If a User is to supply a custom path (specific for them-self) then you will need to ensure that the path will be created so that it exists before the file is created and written to. Another thing to consider is that there may also be a requirement of Permissions to be granted to the application so as to carry out the task within the local file system (if this is where the file is to be created) or wherever else.
Obviously you will need to provide a means for the User to supply a path and file name. The best way to do this in my opinion is with a Folder Chooser dialog. The reason I say this is because it's not nearly as prone to mistakes (spelling, system format, etc.) as it would be if the User completely typed in the desired path and file name. Basically, the less control you give the User to the local file system the better off your application will be. Creating a folder (directory) based on the User Name within a preset file system location and utilizing a User name and date/time for file names is relatively common and can be all auto-generated, this eliminates the possibility of any file system errors. If possible, your application should maintain full control of such a task but understandably this can not always be the case for specific applications where User interaction with the file system is a requirement due to the fact that it's sole functionality is based on such interaction. For example, a File System Explorer where creating, cutting, copying, pasting, deleting, and editing of files and folders is its allowable purpose.
To demonstrate what I'm talking about I have quickly created a small Java runnable you can play with to see how the provided custom methods work. The code is well commented so that you can understand what is going on. Unfortunately all the commenting makes the code file look large when in fact it isn't if you were to remove them.
For simplicity, when we run the provided little console application it first asks for a User's Name. This Users Name will serve two specific purposes.
1) It will be used to create a specific Folder for that particular User only.
2) When a file is saved the file name will start with the User's name and then be followed with date/time data and finally contain the
".txt" file name extension.
Again, just for example purposes, as soon as a User enters his/her name into the console a Folders Selection dialog window will be displayed so that a main folder location can be selected where Users sub-folders will be created in and ultimately the Users data files will be saved under his/her folder name.
The file data is automatically saved once the home folder is selected and the console indicates of success once completed. Our little console application then simply ends.
Here is the Java runnable:
package choosefolder;
import java.awt.Component;
import java.awt.Container;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.time.LocalDateTime;
import java.util.Scanner;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
public class ChooseFolder {
// Declare & initialize required String variables.
// These are global to the entire Class.
private static String userName = "";
private static String savePath = "";
public static void main(String[] args) {
//Setup a Scanner to get User input from console.
Scanner input = new Scanner(System.in);
// Ask for and get the Users Name and convert it to lowercase
// so as to maintain nameing consistency. No sense having "John Doe"
// one day and "john Doe" the next. This would consider 2 different
// Users since the second name has a lowercase "j" in "John" and
// therefore create two separate folder. Converting all supplied names
// to lowercase eliminates this problem.
while (userName.equals("")) {
System.out.println("\nPlease Enter User Name:");
userName = input.nextLine().toLowerCase();
}
// Open a Folders selection dialog window and allow for
// selection of a home folder where all Users saved files
// will be saved. Once we get the selected folder we then
// add the Users name to the path and create it.
savePath = openFoldersDialog("", "Select Home Folder To Save User Folders & File In...") + "\\" + userName;
// Create a special path just for the Users Name (ie: C:\My Files\John Doe\).
if (!createPath(savePath)) {
// If false was returnd from the createPath() method
// then there was an error while creating the path.
// Read console output to get the Error.
System.out.println("\n\u001B[31mError creating Users Save Path! Exiting...\u001B[39;49m");
System.exit(0);
}
// Get the local date & time so we can add it to
// our file namewhen its created.
LocalDateTime dateTime = LocalDateTime.now();
// Create the new file path & file name. This will be the path
// and file name where our Users data will be saved. We take the
// Users path e created and then add the file name to the end. The
// file name consists of the supplied User's name, the date/time and
// the .txt file name extension. Unwanted characters are also removed
// from the Date/Time. So, our path may look something like:
// C:\My Files\John Doe-20160419T155758107.txt
String filePathAndName = savePath + "\\" + userName + "-" +
dateTime.toString().replace("-", "").
replace(":", "").replace(".", "") + ".txt";
// Here is your code. This is the data saved to file....
int[][] sudokuNumbers = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
try (
// Notice the filePathAndName variable which we declared and initialized above.
PrintStream output = new PrintStream(new File(filePathAndName));) {
for (int i = 0; i < sudokuNumbers.length; i++) {
String s= "";
for (int j = 0; j < sudokuNumbers[i].length; j++) {
s+= "|" + sudokuNumbers[i][j] + "|";
}
output.println(s);
}
output.close();
}
catch (FileNotFoundException e) {
System.out.println(e.getMessage());
}
finally {
// Notify in console of success.
System.out.println("\n\u001B[34mFile Saved SUCCESSFULLY in:\n\u001B[39;49m" + filePathAndName);
}
}
/**
* Opens a Folder (directory) selection dialog window. All parameters for this method are optional but if you supply
* something to the last parameter then you must supply something to the parameters preceding it even if it's a null
* string ("") (see usage examples below)
*
* Example Usage:
*
* openFolderDialog(); //Allowed
* openFolderDialog("C:\\"); //Allowed
* openFolderDialog("", "Select The Folder You Need"); //Allowed
* openFolderDialog("", "", "Select Folder"); //Allowed
* openFolderDialog("", "", ""); //Allowed
* openFolderDialog( , "Your Folders, "Select Folder"); //Not Allowed
* openFolderDialog( , , "Select Folder"); //Not Allowed
* openFolderDialog("C:\\", "Your Folders, ); //Not Allowed
*
* I think you get the idea here.
*
* #param dialogOptions OPTIONAL - 3 String Type Parameters
* startPath - String - Default is the running applications' home folder. The path
* supplied here tells the dialog where to start displaying folders from.
* If you just want to use default but would like to supply something to
* the other optional parameters then just supply a null string ("") here.
*
* dialogTitle - String - Dialog Window Title - Default is "Select Folder...". You can
* supply whatever title you like for the dialog window.
*
* openButtonCaption - String - The Select Button Caption - Default is "Select". You can supply
* whatever you like for a button caption and it can be more than one word
* but do keep in mind that the button increases in length to accommodate the
* the supplied string.
*
* #return The selected Folder (directory) full path as string. Returns Null is nothing was selected.
*/
public static String openFoldersDialog(String... dialogOptions) {
String startPath = "";
try {
startPath = new File(".").getCanonicalPath();
} catch (IOException ex) {}
String dialogTitle = "Select Folder...";
String openButtonCaption = "Select";
if (dialogOptions.length != 0) {
if (dialogOptions.length >= 1) {
if (!dialogOptions[0].isEmpty()) { startPath = dialogOptions[0]; }
}
if (dialogOptions.length >= 2) {
if (!dialogOptions[1].isEmpty()) { dialogTitle = dialogOptions[1]; }
}
if (dialogOptions.length == 3) {
if (!dialogOptions[2].isEmpty()) { openButtonCaption = dialogOptions[2]; }
}
}
File sp = new File(startPath);
JFileChooser fc = new JFileChooser(sp);
//Remove the "Files Type:" combo box and label from dialog.
removeJFCFilesTypeComponent(fc);
fc.setDialogTitle(dialogTitle);
fc.setAcceptAllFileFilterUsed(false);
fc.setRequestFocusEnabled(true);
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
int userSelection = fc.showDialog(null, openButtonCaption); //.showSaveDialog(null);
if (userSelection == JFileChooser.APPROVE_OPTION) {
String f = fc.getSelectedFile().getPath();
return f;
}
return null;
}
/**
* Removes the Filter ComboBox from the supplied JFileChooser object.
* This helps make the JFileChooser dialog window look more like a
* folder selection dialog.
* #param filechooser The object name of the JFileChooser object.
*/
public static void removeJFCFilesTypeComponent(Container filechooser) {
Component[] components = filechooser.getComponents();
for (Component component : components) {
if (component instanceof JComboBox) {
Object sel = ((JComboBox) component).getSelectedItem();
if (sel.toString().contains("AcceptAllFileFilter")) {
component.setVisible(false);
// OR
//con.remove(component);
}
}
if (component instanceof JLabel) {
String text = ((JLabel) component).getText();
if (text.equals("Files of Type:")) {
component.setVisible(false);
// OR
//con.remove(component);
}
}
if (component instanceof Container) {
removeJFCFilesTypeComponent((Container) component);
}
}
}
/**
* Creates the supplied path into the local file system if it doesn't already exist. You must
* make sure permissions are properly in place before using this method otherwise the path may
* not be created within the file system.
* #param pathString The path to create (must not include a file name). Will not create path if it already exists.
* #return Boolean True if successful and Boolean False if not.
*/
public static boolean createPath(String pathString) {
if (pathString.isEmpty()) { return false; }
if (doesFolderExist(pathString)) { return true; }
try {
File foldersToMake = new File(pathString);
foldersToMake.mkdirs();
return true;
}
catch (Exception ex) {
System.out.println("*** CreatePath() - Exception Encountered:\n*** " + ex.getMessage());
return false;
}
}
/**
* This method simply return boolean true or false if the supplied path exists.
* #param folderPathString (String) The full path to check. No File Name is allowed
* to be in this path.
* #return
*/
public static boolean doesFolderExist(String folderPathString) {
boolean tf = false;
try {
if (folderPathString.isEmpty() || "".equals(folderPathString)) { return tf; }
File f = new File(folderPathString);
if(f.exists() && f.isDirectory()) { tf = true; }
}
catch (Exception e) {
System.out.println("*** DoesFolderExist() - Exception Encountered:\n*** " + e.getMessage());
tf = false;
}
return tf;
}
}
I hope this helps you (and others).

Check if a resource already exists with a different case

In a wizard I am creating a package and trying to check if a resource already exists with a different case to avoid ResourceException thrown by org.eclipse.core.internal.resources.Resource#checkDoesNotExist. For example I get this exception when I try to create a package com.Example.test while com.example.test already exist. So I would like to make a check for the every segment of the package name. The case for existing com.Example.test is already handled in my code.
Since the method checkDoesNotExist is in the internal class and not declared by IResource it's not in the public API and I cannot use it to make the check before calling IFolder#create. The method IResource#exists is useless in this case because it's case sensitive.
Currently I have the following solution:
/**
* This method checks if a package or its part exists in the given source folder with a different case.
*
* #param pfr A source folder where to look package in.
* #param packageName Name of the package, e.g. "com.example.test"
* #return A String containing path of the existing resource relative to the project, null if the package name has no conflicts.
* #throws CoreException
* #throws IOException
*/
public static String checkPackageDoesExistWithDifferentCase(IPackageFragmentRoot pfr, String packageName)
throws CoreException, IOException
{
IPath p = pfr.getResource().getLocation();
String[] packagePathSegments = packageName.split("\\.");
for (int i = 0; i < packagePathSegments.length; i++)
{
p = p.append(packagePathSegments[i]);
File f = new File(p.toString());
String canonicalPath = f.getCanonicalPath();
if (f.exists() && !canonicalPath.equals(p.toOSString()))
return canonicalPath.substring(pfr.getJavaProject().getResource().getLocation().toOSString().length() + 1);
}
return null;
}
The issue about this solution is that it would work only on Windows, since f.exists() would return false on case-sensitive filesystems.
As you pointed out, there is not API, but you can check the parent resource if it has a member with the same (case-insensitive) name like this:
IContainer parent = newFolder.getParent();
IResource[] members = parent.members();
for( IResource resource : members ) {
if( resource.getName().equalsIgnoreCase( newFolder.getName() ) ) {
...
}
}

How to use H2 databases with a custom file extension?

I'm building an application which stores its data in H2 database files, so my save files have the extension .h2.db.
When opening the application, the user has to select which save file should be loaded. To make it easier to recognize these files, is it possible to tell H2 to use custom file extensions?
Looking at the Database URL overview of H2, I can only specify the name of the database. I would prefer a extension like .save over the default .h2.db. Is there a reasonable way to achieve this?
A workaround would be to link the *.save-file to a temporary folder, renaming it to the correct suffix. If this is the only solution, I guess I would go with the default extension.
H2 database supports pluggable file system so with a bit of extra code you can use any extension you want. You just need to create a wrapper, register it and use your own database URL. The wrapper could look like this:
package my.test;
import org.h2.store.fs.FilePath;
import org.h2.store.fs.FilePathWrapper;
public class FilePathTestWrapper extends FilePathWrapper {
private static final String[][] MAPPING = {
{".h2.db", ".save"},
{".lock.db", ".save.lock"}
};
#Override
public String getScheme() {
return "save";
}
#Override
public FilePathWrapper wrap(FilePath base) {
// base.toString() returns base.name
FilePathTestWrapper wrapper = (FilePathTestWrapper) super.wrap(base);
wrapper.name = getPrefix() + wrapExtension(base.toString());
return wrapper;
}
#Override
protected FilePath unwrap(String path) {
String newName = path.substring(getScheme().length() + 1);
newName = unwrapExtension(newName);
return FilePath.get(newName);
}
protected static String wrapExtension(String fileName) {
for (String[] pair : MAPPING) {
if (fileName.endsWith(pair[1])) {
fileName = fileName.substring(0, fileName.length() - pair[1].length()) + pair[0];
break;
}
}
return fileName;
}
protected static String unwrapExtension(String fileName) {
for (String[] pair : MAPPING) {
if (fileName.endsWith(pair[0])) {
fileName = fileName.substring(0, fileName.length() - pair[0].length()) + pair[1];
break;
}
}
return fileName;
}
}
Then you'll need to register it:
FilePathTestWrapper wrapper = new FilePathTestWrapper();
FilePath.register(wrapper);
And use database URL like this:
"jdbc:h2:save:./mydatabase"
Note the "save:" prefix, it should match string returned by getScheme() method. I put a bit more details here: http://shuvikov.net/blog/renaming-h2-database-files
You cannot define a new extension (suffix) for the H2 database name (page file)
In my code i have a similar need (user may select the database) and this is what i do:
File file=new FileImport(shell, "*"+org.h2.engine.Constants.SUFFIX_PAGE_FILE).filePicker();
if (file!=null){
String database=file.getAbsolutePath().
replace(org.h2.engine.Constants.SUFFIX_PAGE_FILE, "");
...
}
NOTE: FileImport is a class writen by me that extends SWT FileDialog: https://code.google.com/p/marcolopes/source/browse/org.dma.eclipse/src/org/dma/eclipse/swt/dialogs/file/FileImport.java
The filename extensions (notice there are many, not just .h2.db) are set in Constants.java:
/**
* The file name suffix of page files.
*/
public static final String SUFFIX_PAGE_FILE = ".h2.db";
And used directly throughout the codebase, e.g. in Database.java:
/**
* Check if a database with the given name exists.
*
* #param name the name of the database (including path)
* #return true if one exists
*/
static boolean exists(String name) {
if (FileUtils.exists(name + Constants.SUFFIX_PAGE_FILE)) {
return true;
}
return FileUtils.exists(name + Constants.SUFFIX_MV_FILE);
}
So no, you cannot specify a custom file extension for the database. You'll be much better off either not worrying about this, or using a FileNameExtensionFilter to limit your user's choices, like JoopEggen suggested.
You could zip up your database files and rename the .zip to some other extension like .save. Then you unzip your .save file in order to get to the .h2.db file. Note this is how Jars work - they're just zip files under the covers.
You might also look at the Game Developer Stack Exchange, and consider posting a question there like "How do I create user-friendly save files" and describe your problem if you really can't expect your users to tolerate .h2.db files.

Java reflection and manifest file in jar

I would like to (and I don't know if it's possible) do something if jarA is in my classpath and do something else if jarB is in my classpath. I am NOT going to be specifying these jars in the Netbeans project library references because I don't know which of the jars will be used.
Now including the jar in my Netbeans project library references works when I try to use the jar's classes through reflection. But when I remove the netbeans project library reference but add the jar to my classpath the reflection does not work.
My question is really 1) can this be done? 2) Am I thinking about it correctly 3) How come when I specify -cp or -classpath to include the directory containing the jar it doesn't work? 4) How come when I specify the directory in my manifest.mf in the jar file it doesn't work?
Please let me know. This is really bothering me.
Thanks,
Julian
on point 3 - you should include the fully qualified jar name in your classpath, not just the directory.
I believe so!
ClassLoader.getSystemClassLoader()getURLs();
This will tell you which Jar files are in your classpath. Then do X or Y as you please.
A classpath can reference a directory that contains .class files, or it can reference a .jar file directly. If it references a directory that contains .jar files, they will not be included.
java -help says this about -classpath: "list of directories, JAR archives,
and ZIP archives to search for class files." This is very clear that a directory on the classpath is searched for class files, not JAR archives.
This is how I'm doing it. The inner class encapsulates the singleton instance of the logger and its trace method (heh - I know - a singleton inside a singleton). The outer class only uses it if the special class can be loaded, otherwise we go on without it. Hopefully you can modify this to suit your needs. And any suggestions as to better code are always appreciated! :-) HTH
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Provides centralized access to standardized output formatting. Output is sent to System.out and,
* if the classpath allows it, to the Cisco CTIOS LogManager. (This is NOT a dependency, however.)
*
*/
public class LogWriter
{
protected static LogWriter me = null;
private SimpleDateFormat dateFormat = null;
private StringBuffer line = null;
CLogger ciscoLogger = null;
/*
* The following 2 methods constitute the thread-safe singleton pattern.
*/
private static class LogWriterHolder
{
public static LogWriter me = new LogWriter();
}
/**
* Returns singleton instance of the class. Thread-safe. The only way to get one is to use this.
*
* #return an instance of LogWriter
*/
public static LogWriter sharedInstance()
{
return LogWriterHolder.me;
}
#SuppressWarnings("unchecked")
LogWriter()
{
dateFormat = new SimpleDateFormat("yyyyMMddHHmmss ");
line = new StringBuffer();
try {
Class x = Class.forName("com.cisco.cti.ctios.util.LogManager");
if( x != null ) {
java.lang.reflect.Method m = x.getMethod("Instance", new Class[0]);
java.lang.reflect.Method n = x.getMethod("Trace", int.class, String.class );
if( m != null ) {
Object y = m.invoke( x , new Object[0] );
if( n != null ) {
ciscoLogger = new CLogger();
ciscoLogger.target = y;
ciscoLogger.traceImpl = n ;
}
}
}
} catch(Throwable e )
{
System.err.println( e.getMessage() ) ;
e.printStackTrace();
}
}
/**
* Formats a line and sends to System.out. The collection and formatting of the text is
* thread safe.
*
* #param message The human message you want to display in the log (required).
* #param hostAddress Host address of server (optional)
* #param hostPort Port on hostAddresss (optional) - also used for className in object-specific logs.
*/
public void log( String message, String hostAddress, String hostPort )
{
if ( message == null || message.length() < 3 ) return;
synchronized( this )
{
try {
line.delete(0, line.length());
line.append(dateFormat.format(new Date()));
line.append(hostAddress);
line.append(":");
line.append(hostPort);
line.append(" ");
while (line.length() < 28)
line.append(" ");
line.append(message);
this.write( line.toString() );
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void write(String line )
{
System.out.println( line ) ;
}
/**
* Write a simple log message to output delegates (default is System.out).
* <p>
* Will prepend each line with date in yyyyMMddHHmmss format. there will be a big space
* after the date, in the spot where host and port are normally written, when {#link LogWriter#log(String, String, String) log(String,String,String)}
* is used.
*
* #param message What you want to record in the log.
*/
public void log( String message )
{
if( ciscoLogger != null ) ciscoLogger.trace(0x01, message );
this.log( message, "", "");
}
class CLogger
{
Object target;
Method traceImpl;
#SuppressWarnings("boxing")
public void trace( int x, String y )
{
try {
traceImpl.invoke( target, x, y) ;
} catch( Throwable e ) {
// nothing to say about this
}
}
}
}

Categories

Resources