Home | FAQ | Contact me

Perverse File I/O in Java

Sometimes an old C programmer just feels like his wings are clipped. In this case, I wanted to inject a test value (an XML file in, fact) directly into my servlet code to test something. This proved harder than imagined even though I've known vaguely how pernicious random file operations in Java can be. This has frightened me off before.

File I/O in Java: What's what?

The following code results in a file, x.dat, getting created in my Eclipse installation subdirectory:

File g = new File( "/x.dat" );
g.createNewFile();
String path = g.getCanonicalPath();

After the above code runs, in the debugger (and on disk) I see path is:

C:\x.dat

I haven't tried this on Linux.

File g = new File( "x.dat" );
g.createNewFile();
String path = g.getCanonicalPath();

After the above code runs, in the debugger (and on disk) I see path is:

C:\eclipse\x.dat

This is extraordinarily remote from my Eclipse project, but C:\eclipse does happen to be ECLIPSE_HOME.

The question is then, how can I create a file, like x.dat, in my Eclipse project and open it from my code? What is the path to it if I create the file in Eclipse next to the Java code file consuming it? Or somewhere else?

This surely isn't something I wish to do long-term, but it would be nice to inject something out of a file ad hoc during code development just to see how something behaves rather than have to code it in. (The case that prompted me to try this is the injection of a dummy reply to my JAXB servlet's XML GET operation.)

A solution for injecting a file into code

I got the fundamental suggestion on how to use Class methods to accomplish this from a response in the Java Ranch forum.

The clever part is using a bit of reflection to find out where we are so we can locate a file proximal to where we're doing this from. Otherwise, we can put the file at the root ("C:\x.dat") or dump it into wherever we put Eclipse (for example, "C:\eclipse\x.dat") and, in that case, it will work only while we're running in Eclipse. (Well, that's the only time I'd use this anyway, but you see what I mean.)

First, here's the main Javadoc header; the rest of the Javadoc is down inside the class.

This class gets a URI (or just a string) that is relative to the calling class such that, if a file "x.dat" is in the same subdirectory as the class, or on a known, relative path to it, this can be used to open or create a file in the same filesystem subdirectory as or nearby to the class.

Example:

public MyClass
{
   ...
   ClassToURI curi = new ClassToURI( getClass() );
   URI        uri  = curi.toURI( "x.dat" );
   File       file = new File( uri );
   ...

Note: To give an idea of what's coming back, the URL (on Windows) of the class specified when run in Eclipse is:

file:/C:/Users/myuser/dev/workspaces/MyWorkspace
       /.metadata/.plugins/org.eclipse.wst.server.core/tmp0
       /wtpwebapps/MyProject/WEB-INF/classes
       /com/mycompany/myapp/MyClass.class

The toString() method removes the "file:/" prefix. The toURI() method uses the whole thing to turn it into a URI. You can further munge the returned string, but probably not the URI without more hassle. It's up to you which one you prefer to get back.

As shown, java.io.File has constructors that accept String or java.net.URI.

Warning: It is not anticipated that this utility would be used in genuine production code, but probably only as a hack to do file I/O during exploration and/or testing phases. At least, that's how I'm using it. If there were ever a reason to do otherwise, then Java would give us a proper interface for all of this, n'est-ce pas ?

ClassToURI.java:
package com.etretatlogiciels.utilities;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

/**
 * This class gets a URI (or just a string) that is relative to the calling class such that,
 * if a file "x.dat" is in the same subdirectory as the class, or on a known, relative path
 * to it, this can be used to open or create a file in the same filesystem subdirectory as
 * or nearby to the class.
 *
 * @author Russell Bateman
 */
public class ClassToURI
{
	private String	classname = null;			// name of the class (including ".class")
	private URL		classUrl  = null;			// initial URL obtained from Class interfaces
	private String	urlPath   = null;			// the result, intermediate or final, of this utility

	/* Note that, when fully processed, urlPath should begin with PREFIX and end
	 * in "/" (since the classname will have been chopped off).
	 *
	 * TODO: What to do if the prefix is missing? Does this every happen? Be prepared
	 * to come back in here and fix this case.
	 */

	private static final String	PREFIX = "file:/";

	/**
	 * Set up instance for getting a URI to the class. Also, this does the actual
	 * massaging of the URL into a proper string ready to use for whatever purpose
	 * we have in mind (getting a URI or merely returning the string).
	 *
	 * If this constructor fails, look to the specified class passed as argument.
	 *
	 * @param c - class for which this munging around is desired.
	 */
	public ClassToURI( Class< ? > c )
	{
		// determine the classname and get a URL to it...
		this.classname = c.getSimpleName() + ".class";
		this.classUrl  = c.getResource( this.classname );

		// locate the classname in the path and strip it out...
		String	str = this.classUrl.toString();
		int		pos = str.indexOf( this.classname );

		// this is the path we're going to work with...
		this.urlPath = str.substring( 0, pos );
	}

	private String renderAsString( String filename )
	{
		String	string = this.urlPath;

		if( filename != null )
			string += filename;

		return string;
	}

	/**
	 * This returns the path as a string rather than a URI. If this method fails
	 * to return a usable string, it is probably the utility's fault, but it
	 * could also be the argument passed.
	 *
	 * In the absence of an argument, the returned path should end in a directory
	 * delimiter.
	 *
	 * @param filename - name to add to the end of the parent subdirectory derived
	 * 		from the class when we created a new instance of it.
	 */
	public String toString( String filename )
	{
		String	string = renderAsString( filename );

		if( string.startsWith( PREFIX ) )
			string = string.substring( PREFIX.length() );

		return string;
	}

	/**
	 * This method returns the promised URI including the filename passed in.
	 * If none, then the URI is to the parent (directory) of the class we were
	 * called with.
	 *
	 * @param filename - name to add to the end of the parent subdirectory derived
	 * 		from the class when we created a new instance of it.
	 *
	 * @throws MalformedURLException probably a mistake in this utility.
	 * @throws URISyntaxException probably a mistake in this utility.
	 */
	public URI toURI( String filename ) throws MalformedURLException, URISyntaxException
	{
		String	string = renderAsString( filename );
		URL		url    = new URL( string );

		return url.toURI();
	}
}
// vim: set tabstop=3 shiftwidth=3 noexpandtab: