Accessibility
 
 
Leveraging Java Classes from ColdFusion
Daryl Banttari
Sr. Consultant
Macromedia

Overview
This article shows you how to leverage built-in Java classes to read a file line-by-line. The goal is to simply display a file "line by line," but these techniques could easily be extended for any purpose that involves reading a large file line by line, such as a database import from a large text file.

While <CFFILE> could be used for this purpose, <CFFILE> reads the entire file into memory at once, which could cause severe memory problems on the server hosting ColdFusion. Fortunately, ColdFusion makes accessing Java classes, such as Java's FileReader and BufferedReader classes, simple and easy.

To read a file line-by-line in Java, you would typically create a FileReader object, then pass that object to BufferedReader and use the BufferedReader.readLine( ) method to read the file, line by line.

Extending a Java Class
A slight problem occurs when using BufferedReader.readLine( ) from ColdFusion. When BufferedReader reaches the end of the file, the readLine( ) method returns a Java null value. However, ColdFusion does not have an equivalent to a Java null (or a database null), so ColdFusion instead returns an empty (zero-length) string. We are left with no way to differentiate between a blank line in the file and the null returned by readLine( ) when it reaches the end of the file.

(More information on BufferedReader can be found at:
http://java.sun.com/j2se/1.4/docs/api/java/io/BufferedReader.html)

The solution is to create a subclass of BufferedReader in Java and use it instead. We will name that class "CFBufferedReader," and its job will be to keep track of the responses from BufferedReader to detect the Java null that signals the end of the file, making that value accessible from ColdFusion via a different method we'll call isEOF( ).

Here is our commented Java source (CFBufferedReader.java):

//CFBufferedReader
// Purpose:
// Since ColdFusion automatically turns "null" Strings into empty strings,
// it is necessary to extend BufferedReader so that we can detect
// the null string returned from readLine().
//
// Use:
// CFBufferedReader works exactly like java.io.BufferedReader,
// but adds an isEOF() method to indicate whether the last call
// to readLine detected the end of the file.
//
// Author:
// Daryl Banttari (Macromedia)
//
// Copyright: (c)2001 Macromedia
// Feel free to use this for any purpose, with or without inclusion
// of this copyright notice, so long as you agree to hold Macromedia
// completely harmless for any use of this code.
// We are using several classes from java.io,
// so import everything under java.io:
import java.io.*;

// Define this class as a subclass of java.io.BufferedReader
public class CFBufferedReader extends java.io.BufferedReader {
// variable to hold the EOF status of the last read
// default to false; we'll assume you're not at eof if // we haven't read anything yet private boolean eof = false; // our class constructors will simply pass the arguments // through to our superclass, BufferedReader: public CFBufferedReader(Reader in, int sz) { // "super" is an alias to the superclass. // calling super() in this fashion actually // invokes the superclass' constructor method. super(in, sz); } public CFBufferedReader(Reader in) { super(in); } // here we extend the readLine method: public String readLine() throws java.io.IOException { String curLine; // call the "real" readLine() method from the superclass curLine = super.readLine(); // now set eof to "is curline null?" // note that there are two equals signs between "curLine" and "null" eof = (curLine == null); // return curline to the caller "as is" return curLine;
} public boolean isEOF() {
// simply return the current value if the eof variable return eof;
} }

Our next step is to place the Java source code file, CFBufferedReader.java, someplace where we can find it. I have Java installed in C:\jdk1.3.1\, so I'll place the file in C:\jdk1.3.1\jre\lib\ .

Now compile it. In the example below, I've used the verbose option; without it, javac emits no messages, which can be unsettling:

C:\jdk1.3.1\jre\lib>C:\jdk1.3.1\bin\javac -verbose CFBufferedReader.java
[parsing started CFBufferedReader.java]
[parsing completed 120ms]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/io/BufferedReader.class)]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/io/Reader.class)] [loading C:\jdk1.3.1\jre\lib\rt.jar(java/lang/Object.class)] [loading C:\jdk1.3.1\jre\lib\rt.jar(java/lang/String.class)]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/io/IOException.class)]
[checking CFBufferedReader]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/lang/Error.class)]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/lang/Exception.class)]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/lang/Throwable.class)]
[loading C:\jdk1.3.1\jre\lib\rt.jar(java/lang/RuntimeException.class)]
[wrote CFBufferedReader.class]
[total 481ms] C:\jdk1.3.1\jre\lib>

This creates our "compiled" class file, CFBufferedReader.class.

Configuring ColdFusion for Java
The next step is to configure ColdFusion to load the Java VM properly. This step is done from the "JVM and Java Settings" tab of the ColdFusion Administrator.

Remember: my Java installation is in C:\jdk1.3.1\. If your Java installation is in a different directory, or if you are running on a non-Windows platform, you will need to set these values accordingly. More information on this can be found in the ColdFusion online documentation set "Installing and Configuring ColdFusion Server" under the topic "Basic ColdFusion Server Administration," "Extensions." (If you're running a Unix variant, you should also review our knowledgebased article 20198 at:
http://www.allaire.com/Handlers/index.cfm?ID=20198&Method=Full)

The settings that I used are as follows:

Java Virtual Machine Path: C:\jdk1.3.1\jre\bin\hotspot\jvm.dll
Class Path: C:\jdk1.3.1\jre\lib\

Using Java to Read the File
At this point, I will create a ColdFusion template, "javaFileTest.cfm." This template creates a Java FileReader object that allows us to read a file byte by byte. We will then pass that FileReader object to our CFBufferedReader object, and then use CFBufferedReader.readLine( ) to automagically read the file line by line. In this example, the template will read itself (using cgi.cf_template_path to get its full path and filename), and then display each line after replacing "<" with "&lt;":

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
<head>	
	<title>Java File Example</title>
</head>
<body>
<!--- get our filename from the CGI environment --->
<cfset fn = cgi.cf_template_path>
<!--- create a Java FileReader object to read this file --->
<!--- note that java.io.FileReader is a native Java class.--->
<cfobject type="Java" class="java.io.FileReader" name="fr" action="create">
<!--- when calling Java from CF, the constructor is called "init()" --->
<cfset fr.init(fn)>
<!--- now pass the FileReader object to our extended BufferedReader --->
<cfobject type="Java" class="CFBufferedReader" name="reader" action="create">
<cfset reader.init(fr)>
<!--- read the first line from the file --->
<cfset curLine=reader.readLine()>
<!--- now loop until we reach the end of the file --->
<cfloop condition="not #reader.isEOF()#">
	<!--- display the current line --->	
	<cfoutput>#replace(curLine,"<","&lt;","ALL")#<br>
	</cfoutput>	
	<!--- flush the output buffer (cf5 only) --->	
	<cfflush>	
	<!--- each call to readLine( ) reads the /next/ line,
 	  until the end of the file is reached, at which point
      isEOF( ) starts returning "true" --->	
	<cfset curLine=reader.readLine()>
</cfloop>

</body>
</html>

Documentation on java.io.FileReader can be found at: http://java.sun.com/j2se/1.4/docs/api/java/io/FileReader.html

Conclusion
ColdFusion makes leveraging Java classes easy, which greatly increases the reach of what is possible from within a ColdFusion page. Additionally, we have seen that it is easy to extend native Java classes into custom subclasses that are specifically tailored to an application.