user746461
user746461

Reputation:

Working directory when run in eclipse, cmd, and jar

I'm writing a Java program which will execute an external file ~/Java/exampleProject/bin/import.sh. My program is under package gqqnbig. So the directory structure is

exampleProject/
  bin/
    import.sh
    gqqnbig/
      *.class

When I debug the program in eclipse, the working directory is ~/Java/exampleProject/. I have to execute bin/import.sh.

When I run the program in cmd, the current directory is ~/Java/exampleProject/bin, my code will not find import.sh.

The program has to be portable (distribute with import.sh). With the correct directory structure, it should work in my computer as well as in your computer, so I cannot hard code the path of import.sh.

I also want to pack it into a single jar file. The desired structure is (Figure 1)

bin/
    import.sh
    program.jar

So how can my program find import.sh when run in eclipse, cmd and jar?

UPDATE

I ask my question in another way. Please implement getAbsolutePath function, so that no matther the code is running in eclipse, in cmd, or as a jar file in a folder which also has import.sh (See Figure 1), the output is identical.

public static void main(String[] args)
{
    System.out.println("Look for "+getAbsolutePath()+"\\import.sh");
}

Upvotes: 1

Views: 1613

Answers (3)

indivisible
indivisible

Reputation: 5012

Here's a method pulled from one of my projects. It get's the folder that the jar file is located in as opposed to the directory if was run from if invoked on the command line.

/**
 * Retrieve a File representation of the folder this application is
 * located in.
 * 
 * @return
 */
private static File getApplicationRootFolder()
{
    String path = FileGetter.class.getProtectionDomain().getCodeSource()
            .getLocation().getPath();
    try
    {
        String decodedPath = URLDecoder.decode(path, "UTF-8");
        File jarParentFolder = new File(decodedPath).getParentFile();
        if (jarParentFolder.exists() && jarParentFolder.canRead()
        {
            File shellScript = new File(jarParentFolder, "import.sh")
    }
    catch (UnsupportedEncodingException e)
    {
        Main.myLog.error(TAG, "Unencoding jar path failed on:\n\t" + path);
        e.printStackTrace();
        return null;
    }
}

You can then use that directory to make a File object for your shell script File shellScript = new File(getApplicationRootFolder(), scriptFilename);


EDIT: Follow up questions to try to help you out and a solution

So you want to be able to access one file that has three locations depending on when/where you code is run. This is how I see those cases:

Case 1: Running directly from Eclipse (unpackaged code):

shell script:    X:/Java/exampleProject/bin/import.sh
class file:      X:/Java/exampleProject/bin/gqqnbig/YourClass.class

Case 2: Running the packaged jar (shell script inside):

shell script:    X:/Java/YourJar.jar/bin/import.sh
class file:      X:/Java/YourJar.jar/bin/gqqnbig/YourClass.class

Case 3: Running the packaged jar (shell script external):

shell script:    X:/Java/import.sh
class file:      X:/Java/YourJar.jar/bin/gqqnbig/YourClass.class

What I think you need to do is prioritise the order you look at these locations and fall back to the next one in line if the shell script isn't found. I'd guess you want:

1. external to jar
2. inside packaged jar
3. unpackaged

So to access these you will need to write each separately and move through each until you get File.exists() == true.

Something like what follows. Note I didn't test this and there are likely errors. I'll leave you to sort them out. My code is based on the assumptions made above, again I'll leave you to modify the code based on any incorrect guesses.

So here's a class with one public method taking a filename argument and returning an InputStream. I opted for InputStream in all cases as once you package up your jar you cannot access the resources as File objects any more, only Streams.

public class FileGetter
{

    private static String RESOURCE_DIRECTORY = "bin";

    /**
     * Retrieve an InputStream for a resource file.
     * 
     * @param filename
     * @return
     */
    public InputStream getResourceFileStream(String filename)
    {
        // this is where you decide your preference or the priority of the locations
        InputStream inputStream = null;

        inputStream = getExternalFile(filename);
        if (inputStream != null)
        {
            return inputStream;
        }
        inputStream = getInternalPackagedFile(filename);
        if (inputStream != null)
        {
            return inputStream;
        }
        inputStream = getInternalUnpackagedFile(filename);
        if (inputStream != null)
        {
            return inputStream;
        }

        // couldn't find the file anywhere so log some error or throw an exception
        return null;
    }

    /**
     * Retrieve an InputStream for a file located outside your Jar
     * 
     * @param filename
     * @return
     */
    private static InputStream getExternalFile(String filename)
    {
        // get the jar's absolute location on disk (regardless of current 'working directory')
        String appRootPath = FileGetter.class.getProtectionDomain().getCodeSource()
                .getLocation().getPath();
        try
        {
            String decodedPath = URLDecoder.decode(appRootPath, "UTF-8");
            File jarfile = new File(decodedPath);
            File parentDirectory = jarfile.getParentFile();
            if (testExists(parentDirectory))
            {
                File shellScript = new File(parentDirectory, filename);
                if (testExists(shellScript))
                {
                    return new FileInputStream(shellScript);
                }
            }
        }
        catch (UnsupportedEncodingException e)
        {}
        catch (NullPointerException e)
        {}
        catch (FileNotFoundException e)
        {}
        // if any part fails return null
        return null;
    }

    /**
     * Retrieve an InputStream for a file located inside your Jar.
     * 
     * @param filename
     * @return
     */
    private static InputStream getInternalPackagedFile(String filename)
    {
        // root directory is defined as the jar's root so we start with a "/".
        URL resUrl = FileGetter.class.getResource(File.separator + RESOURCE_DIRECTORY
                + File.separator + filename);
        String badPath = resUrl.getPath();
        String goodPath = badPath.substring(badPath.indexOf("!") + 1);
        InputStream input = FileGetter.class.getResourceAsStream(goodPath);
        // returns null if nothing there so just
        return input;
    }

    private static InputStream getInternalUnpackagedFile(String filename)
    {
        // eclipse will 'cd' to the code's directory so we use relative paths
        File shellScriptFile = new File(RESOURCE_DIRECTORY + File.separator + filename);
        if (testExists(shellScriptFile))
        {
            try
            {
                InputStream shellScriptStream = new FileInputStream(shellScriptFile);
                if (shellScriptStream != null)
                {
                    return shellScriptStream;
                }
            }
            catch (FileNotFoundException e)
            {}
        }
        return null;
    }

    /**
     * Test that a file exists and can be read.
     * 
     * @param file
     * @return
     */
    private static boolean testExists(File file)
    {
        return file != null && file.exists() && file.canRead();
    }
}

But with all that being said a better way to sort this would be to ensure that the file exists on disk and create it if not found. Then execute the script from disk.

Upvotes: 2

user746461
user746461

Reputation:

I composed a solution. Call getExecutablePath() to get unified path.

public static File getExecutablePath()
{

    String workingDirectory = System.getProperty("user.dir");

    File binFile = new File(workingDirectory, "bin");
    if (binFile.exists() && (new File(workingDirectory, "src")).exists())
    {
        return binFile;
    }
    else if (isRunningFromJar())
        return getApplicationRootFolder();
    else
        return new File(workingDirectory);

}

public static boolean isRunningFromJar()
{
    String className = SystemHelper.class.getName().replace('.', '/');
    String classJar = SystemHelper.class.getResource("/" + className + ".class").toString();
    return classJar.startsWith("jar:");
}

/**
 * Retrieve a File representation of the folder this application is located in.
 * 
 * @return
 */
private static File getApplicationRootFolder()
{
    try
    {
        String path = SystemHelper.class.getProtectionDomain().getCodeSource().getLocation().getPath();
        String decodedPath;

        decodedPath = URLDecoder.decode(path, "UTF-8");

        File jarfile = new File(decodedPath);
        return jarfile.getParentFile();
    }
    catch (UnsupportedEncodingException e)
    {
        throw new RuntimeException(e);
    }
}

Upvotes: 0

Kazagha
Kazagha

Reputation: 36

I would like to know a definitive answer for this myself.

As a workaround I would put 'import.sh' inside the exampleProject and change the relative path to 'import.sh'.

In theory that should work inside Eclipse, and as a packaged Jar file with program.jar and import.sh in the same directory.

It won't work on the cmd prompt unfortunately, maybe someone can suggest a better method.

-Kaz

Upvotes: 0

Related Questions