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 "<":
<!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,"<","<","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.