Many applications, both large and small, provide some sort of customization or configuration language. Such languages are used for storing user preferences, defining toolbars or configurable menus, writing macros, or even writing custom extensions. In many cases, these auxiliary languages are hidden from users by configuration dialogs, and they may not even be documented, but they are still there behind the scenes. These application configuration and customization languages are examples of scripting languages. Examples you may be familiar with include AppleScript, its predecessor HyperTalk (the language behind Apple's HyperCard system), various incarnations of LotusScript (used for macros or more extensive extensions in several Lotus programs, including 1-2-3, Word Pro, and Notes), WordBasic (Microsoft Word's macro language), and the DOS batch-file language.
This chapter explains how to use one particular scripting language, Tcl, with your Java applications. Tcl was chosen for four reasons:
Other scripting languages such as JavaScript or Python might also work well with Java, but none share all these characteristics as of this writing. Additionally, although other scripting languages might excel at particular kinds of tasks, I believe that Tcl strikes the best balance among the several different roles that scripting languages are often asked to fill. However, many of the general statements in this chapter about the benefits of using a reusable scripting language will apply to other such languages (see Chapter 27, "Alternatives to Java," for a discussion of several other scripting languages).
The version of the TclJava API described here is version 0.1. Obviously, the interface will change and grow. Rather than spending too much time documenting every detail of an early beta release of the API, this chapter concentrates on the general approach of using Tcl (and scripting languages in general) with Java and the benefits of that approach.
It's common for configuration or command languages to be custom-designed for individual applications. This has several disadvantages:
Tcl was designed as a solution to these problems. The name stands for "tool command language," and Tcl was designed from the start for that role. It was intended to be a reusable, application-neutral language that could be used by a wide variety of applications as a configuration, customization, extension, or command language. The author and designer, John Ousterhout, had been involved in the design of several applications and had grown increasingly frustrated by the distraction of having to design new languages for each of them, by the inadequacies of the resulting languages, and by the burden that the proliferation of languages placed on end users. Tcl is his answer.
Tcl is a simple language, oriented around strings and commands. Rather than having several different data types, Tcl represents all values as character strings, and instead of the usual combination of statements, procedures, functions, and expressions, Tcl code consists almost entirely of commands. Even control structures such as for and while loops are simply commands. In these respects, Tcl is reminiscent of the UNIX shells or the DOS batch-file language, but it is superior in many ways.
Tcl is implemented as a library, so applications written in languages such as C or Java can embed Tcl and use it as a scripting language. An important part of that embedding process is creating new, application-specific Tcl commands that can be used by Tcl scripts to control the application or query its current state.
One crucial aspect of Tcl's design is that application-specific extensions exist on an equal footing with the built-in Tcl facilities, instead of being "second-class citizens." This gives the application developer maximum freedom in designing the interface between the application and Tcl. It's possible to provide application-specific control structures that iterate over some part of the application's data or structure in some useful way. The application also can create "magic" Tcl variables that directly represent portions of the application's state (the Tcl interpreter ties such variables either to C variables or to C accessor functions that are responsible for getting and setting the value).
Like Java, Tcl is a secure language. Tcl enables you to create "padded room" environments in which all potentially dangerous commands have been removed or replaced by safe alternatives. Such environments can be used to execute Tcl code that comes from untrusted sources.
Many applications (including a growing number of commercial programs) make use of Tcl for a wide range of tasks, including site-wide configuration, user configuration and customization, third-party extensions, data storage, and user interface development. Recent releases of Tcl work on Windows, Macintosh, and UNIX platforms.
The Tcl home page is at the following address:
http://www.smli.com/research/tcl/
Because Java is an interpreted, cross-platform language, and especially because it's a relatively high-level language (with sophisticated string handling and built-in data structures, for example), some have reached the conclusion that there's no point in using a scripting language in conjunction with Java. Depending on the details of their applications, they may be right. But in most cases, a scripting language such as Tcl can still be extremely useful in a Java application, because Java has several characteristics that make it ideal for application development, but not so great for the kinds of things at which scripting languages excel.
Variable declarations, static type checking, classes, and packages all make it easier for developers to manage the complexity of a large software project, but such features get in the way when you just want to write a 40-line convenience script or when writing something (such as a user interface prototype) that will be changed numerous times to try different approaches. Java code is compiled in the interest of performance, but that compilation step is a nuisance when performance of the code isn't really a priority. (Site administrators hate having to compile a configuration file after every edit when they are trying to get an application's configuration just right!)
Finally, Java is a complex language and somewhat unusual for most users. Users are not programmers, and they are usually not interested in becoming programmers. Many application users have some familiarity with simple, command-oriented languages such as the following:
SET PATH = C:\DOS;C:\WINDOWS
Scripting languages are usually much better languages than that, but they are in the same simple spirit, and that's helpful to application users.
If you're still skeptical about whether scripting languages really have a place in the Java world, take a look at the following three applets that make use of special-purpose scripting languages:
http://www.cs.hope.edu/~dbrown/java/LEDSign/WWW/LED.html
http://users.aimnet.com/~foureyes/clickboard/ClickBoard.html
http://www.cybercomm.net/~drz/java/movie/
In each case, the applet-specific scripting language is just what you might expect: weak and quirky, just enough to accomplish the tasks the author had in mind with a minimum of implementation effort and no more. The authors of those applets shouldn't be blamed for that; their main interest was in writing the useful applet, not in implementing a scripting language, and that's just the way it should be. They should have been able to use an existing scripting language rather than having to invent new ones.
Perhaps in response to applets such as these, another programmer has implemented a reusable, generic scripting language in Java. Yassl (yet another simple scripting language) is a much better language than these specialized applet control languages, and it might meet your needs quite well. It can be found at the following address:
http://www.blackdown.org/~kbs/yassl/yassl.html
Finally, Netscape's JavaScript language, although currently available only as an integral part of Netscape's Navigator software, is supposed to be available soon as a reusable, embeddable scripting language that interfaces to Java.
That makes three specialized applet configuration languages and two more general scripting languages (Yassl and JavaScript). At the time of this writing, Java has been available to the public for less than a year, and five new scripting languages have already been designed to work with Java applets and applications. A common scripting language available to Java applications would be a real benefit.
The interface between Tcl and Java is quite small and easy to understand. It will no doubt grow larger as it matures, enabling Java programmers access to more of Tcl's special facilities, but the central core of the API will probably always be simple, reflecting the simplicity of Tcl itself. The important concepts of the interface are represented by just two classes: Command and Interp.
Command objects represent Tcl commands. To write a new Tcl command in Java, create a subclass of Command and override the invoke method. Here's an example of a command called Echo, which simply writes its arguments to standard output:
import tcl.*;
public class
EchoCommand extends Command
{
public int
invoke (Interp mytcl, Value args[]) {
// Skip args[0], which is the command name ...
for (int i=1; i < args.length; i++) {
System.out.print((i>0 ? " " : "") + args[i]);
}
System.out.println();
return Interp.TCL_OK;
}
}
Instances of the Interp class represent Tcl interpreters (it's possible, and often useful, to have multiple distinct interpreters in a single application). Upon creation, the interpreter will be automatically initialized with the built-in Tcl commands. Once you create an interpreter and implement a new subclass of Command, you can install the command in the interpreter using the interpreter's createCommand method, supplying an instance of the command object along with the command name:
Interp mytcl = new Interp();
mytcl.createCommand("echo", new EchoCommand());
You can install any number of new commands in an interpreter.
Creating an interpreter and installing new commands are just preparation, of course. Once these are done, you have to supply some code for the interpreter to execute. The interpreter's eval method accepts Tcl code as a parameter (a string) and evaluates it:
Result result = new Result();
mytcl.eval("echo apple peaches mango kumquat", result);
If the EchoCommand class shown previously is installed correctly, it is invoked with four arguments and prints them. Because it doesn't return a useful result, the value in the result variable can simply be ignored. (The value actually returned from eval is an integer that indicates whether the command executed successfully.)
Believe it or not, you've covered the basics of the TclJava API. There are some things omitted, of course, including a way to add the Tk user interface toolkit (a popular Tcl extension) into the interpreter. We'll cover some of those details later, but you now know the fundamental mechanisms for embedding Tcl in Java applications.
Taken together, the preceding code fragments seem rather pointless. First a new Tcl command is created and then an interpreter. The command is installed in the interpreter and then invoked. Why bother with a scripting language if that's all you're going to do with it?
Recall that the point of doing all this is to give users or site administrators some control over the configuration or operation of the program without requiring them to write Java code and modify parts of the program itself. There are two ways in which the examples so far are unrealistic:
The next section provides a more realistic example.
Many graphical applications provide a user-definable menu or toolbar, permitting users to choose certain operations for easy access. This is especially useful in very complicated applications where some operations might appear three levels deep in cascaded menus, for example.
Applications that provide such a facility might require the user to build a menu definition file by hand, or they might provide an interactive mechanism. In the first case, some comprehensible file format is essential, and even in the second case it can be useful. Just because you use Tcl doesn't mean you have to force your users to learn it and build their own configuration files by hand. Even if you provide thorough interactive configuration dialogs, using Tcl as a storage format can save you trouble, and you'll be surprised how often it comes in handy to have access to a full-fledged scripting language in your configuration files.
This example shows how to add a menu definition facility to an application. In the interest of keeping the example reasonably short, the facility is restrictive: Users can only add items to one menu that is set aside for their use (I've given it the label "Quick"), and cascaded submenus are not allowed. However, the example does support a simple extension facility, so a user menu item can represent a composite of several built-in menu actions performed in sequence.
Before going into the details of the implementation, we should decide what the menu definition file is going to look like. We don't really have to design a syntax (Tcl takes care of that), but we do need to know what new Tcl commands we should implement.
I've chosen to implement just one command, quickmenu,
with five subcommands to implement particular actions:
add | Adds a new item to the menu. |
separator | Adds a separator bar to the menu to group related items visually. |
actions | Returns a list of all the actions that the application supports as menu items. |
script | Adds a new item that invokes a Tcl script when the menu item is selected. |
invoke | Adds nothing to the menu-instead, it immediately invokes one of the available actions. This is meant for use in procedures that are attached to the menu using script. |
Here's an example of a menu definition file that uses all the subcommands (although it doesn't do anything intelligent with actions). The actions in the example would be appropriate for a word processor:
# This is Tcl, not Java!
# Add the "Insert Date" action to our menu:
quickmenu item "Insert Date"
# Now add a separator bar.
quickmenu separator
# Get the list of valid actions ...
set actionlist [quickmenu actions]
# ... but don't bother to do anything with it.
# Add a custom menu item which invokes the notepar proc, defined below.
quickmenu script Note notepar
# Here's the notepar proc. It formats the current paragraph
# specially: right aligned and bold.
proc notepar {} {
quickmenu invoke "Select paragraph"
quickmenu invoke "Align right"
quickmenu invoke "Bold"
quickmenu invoke "Unselect"
}
The next job is to implement and install the quickmenu command. In order to do its job, the command needs to know about the menu object it can add items to and the list of actions that the application supports, so we put those in instance variables.
The list of actions will be primarily used for item and invoke subcommands; the implementation needs to see whether the specified action is valid before carrying out the request. If many actions are supported, such checks will be much more efficient if the list is stored as a hash table, rather than a simple array or vector of strings. For this purpose, it's not important what values are stored with the keys in the hash table; the only thing you care about is whether a particular key is in the table.
The list will also be used to implement the actions subcommand. That will be a slower operation using a hash table, but it's reasonable to expect item and invoke to be much more frequent operations than actions, so a hash table seems like a reasonable trade-off.
With those decisions made, we can write the basic form of the QuickMenuCommand class as shown in Listing 29.1.
Listing 29.1. QuickMenuCommand overview (QuickMenuCommand.java part 1).
/*
* QuickMenuCommand.java 1.0 96/03/25 Glenn Vanderburg
*/
package COM.MCP.Samsnet.tjg;
import java.awt.*;
import java.util.*;
import tcl.*;
/**
* An example Tcl command. Provides facilities for users to
* define a special, customizable menu in an application.
*
* Although this class is complete, it requires special support
* in an application to work properly.
*
* @version 1.0, 25 Mar 1996
* @author Glenn Vanderburg
*/
public
class QuickMenuCommand extends Command
{
// The menu we can modify
private Menu quickmenu;
// The set of valid actions for the menu
private Hashtable actions;
/**
* Creates a new QuickMenuCommand instance
* @param qm the menu for the command to modify
* @param actions the set of valid actions for the menu
*/
public
QuickMenuCommand(Menu qm, Hashtable actions) {
quickmenu = qm;
this.actions = actions;
}
// It may be useful to provide alternate constructors which
// take the action list as an array or vector of strings.
// Method: public int invoke(Interp interp, Value args[]) Listing 29.2
// Method: int args_error(String msg) Listing 29.3
}
The invoke method is where the work of the command takes place. It is fairly straightforward, but because it has to provide for five different subcommands, it's somewhat long. Listing 29.2 shows the invoke method.
Listing 29.2. QuickMenuCommand invoke method (QuickMenuCommand.java part 2).
/**
* Processes the command. Determines the subcommand,
* validates parameters, and performs appropriate actions.
* @param interp the Tcl interpreter
* @param args the arguments to the command
*/
public int
invoke(Interp interp, Value args[]) {
if (args.length == 1) {
return args_error(interp, NO_SUBCOMMAND);
}
if ("item".equals(args[1])) {
if (args.length != 3) {
return args_error(interp, WRONG_ITEM);
}
if (actions.containsKey(args[2].toString())) {
quickmenu.add(new MenuItem(args[2].toString()));
return Interp.TCL_OK;
}
else {
interp.setResult(new Value("invalid item: " + args[2]));
return Interp.TCL_ERROR;
}
}
else if ("separator".equals(args[1])) {
if (args.length != 2) {
return args_error(interp, WRONG_SEPARATOR);
}
quickmenu.addSeparator();
return Interp.TCL_OK;
}
else if ("actions".equals(args[1])) {
if (args.length != 2) {
return args_error(interp, WRONG_ACTIONS);
}
// Build a Tcl list of the actions
String list = "{ ";
for ( Enumeration keys = actions.keys();
keys.hasMoreElements();
) {
list += "{" + keys.nextElement() + "} ";
}
list += "}";
interp.setResult(new Value(list));
return Interp.TCL_OK;
}
else if ("script".equals(args[1])) {
if (args.length != 4) {
return args_error(interp, WRONG_SCRIPT);
}
// We use a special MenuItem for this (see below)
quickmenu.add(new TclScriptMenuItem(args[2].toString(),
&nbs p; args[3].toString()));
return Interp.TCL_OK;
}
else if ("invoke".equals(args[1])) {
if (args.length != 3) {
return args_error(interp, WRONG_INVOKE);
}
if (actions.containsKey(args[2].toString())) {
// To pull this off, we have to generate a fake event.
// Furthermore, since the requested action isn't required
// to be an item on the menu (just a valid action) we must
// create a MenuItem which will only last long enough to
// serve as the trigger for this invocation.
Event evt;
evt = new Event(new MenuItem(args[2].toString()),
Event.ACTION_EVENT,
args[2].toString());
quickmenu.postEvent(evt);
return Interp.TCL_OK;
}
else {
interp.setResult(new Value("invalid action: " + args[2]));
return Interp.TCL_ERROR;
}
}
else {
return args_error(interp, BAD_SUBCOMMAND);
}
}
The only really tricky part about that code is the implementation of the invoke subcommand. In a real application, there would almost certainly be a cleaner way to execute an action than by faking a menu invocation.
Before moving on to the TclScriptMenuItem class (used in the implementation of the script subcommand), let's finish QuickMenuCommand by supplying the args_error method, shown in Listing 29.3. Its only purpose is to supply the appropriate Tcl result in event of an error, to keep the code for the invoke method a little cleaner.
Listing 29.3. QuickMenuCommand args_error method (QuickMenuCommand.java part 3).
static final String NO_SUBCOMMAND =
"wrong # args: should be \"quickmenu subcommand ...\"";
static final String WRONG_ITEM =
"wrong # args: should be \"quickmenu item itemname\"";
static final String WRONG_SEPARATOR =
"wrong # args: should be \"quickmenu separator\"";
static final String WRONG_ACTIONS =
"wrong # args: should be \"quickmenu actions\"";
static final String WRONG_SCRIPT =
"wrong # args: should be \"quickmenu script label scriptstring\"";
static final String WRONG_INVOKE =
"wrong # args: should be \"quickmenu invoke itemname\"";
static final String BAD_SUBCOMMAND =
"invalid subcommand: should be one of "
+ "item, separator, actions, command, invoke";
/**
* Performs error handling for the "invoke" method.
* Sets an appropriate message in the interpreter result,
* and returns an error code.
* @param msg the message describing the error.
*/
int
args_error(Interp interp, String msg) {
interp.setResult(new Value(msg));
return Interp.TCL_ERROR;
}
Now there's just one more thing before we can wrap up the quickmenu command and install it: the TclScriptMenuItem class that we used in the implementation of the script subcommand. It's really just an extension of MenuItem that maintains a Tcl script string as an instance variable. The main application menu handling code takes care of the rest (see the next section). Listing 29.4 shows the TclScriptMenuItem class.
Listing 29.4. TclScriptMenuItem.java.
/*
* TclScriptMenuItem.java 1.0 96/03/25 Glenn Vanderburg
*/
package COM.MCP.Samsnet.tjg;
import java.awt.*;
import tcl.*;
/**
* An extension of MenuItem which causes a Tcl script to be run when invoked.
*
* @version 1.0, 25 Mar 1996
* @author Glenn Vanderburg
*/
public
class TclScriptMenuItem extends MenuItem
{
// The script to be run for this menu item.
private String script;
/**
* Creates a new TclScriptMenuItem.
* @param label the label string for the menu item
* @param script the Tcl script to be run when this menu item is invoked.
*/
public
TclScriptMenuItem(String label, String script) {
super(label);
this.script = script;
}
/**
* Returns the Tcl script associated with this menu item.
*/
public String
getScript() {
return script;
}
}
Now the quickmenu command is done and ready to be installed. During application initialization, after the Menu object for the quick menu and the hash table with all the valid actions have been created, the application should create a Tcl interpreter, create an instance of QuickMenuCommand, and install it as a new Tcl command:
Interp tcl = new Interp();
tcl.createCommand("quickmenu", new QuickMenuCommand(quickmenu, actions));
The QuickMenuCommand class provides the support we need for the menu definition file-actually defining the menu and providing a way to learn what actions are allowable in it. However, the command mostly just stores information and modifies the state of the menu. Other parts of the application must cooperate to make it all work.
First, of course, the application must create the menu and install it in the menu bar. It might start out empty, or it might have a small number of predefined entries (possibly including a "What's this?" entry that explains what the menu is and how to customize it). The application must also create the hash table of valid actions. We'll return to that soon.
The largest part of the application's responsibility is actually invoking the menu actions when a user chooses them. All the details of menu handling are not explained here, but typically, menu selections are processed by the action method of some component that contains the menu. When a menu item is selected, an action event is posted to that component, which passes the event to the action method, along with the event's argument (in the case of menu items, the argument is usually the item's label string). In simple applications, the action method examines the label string to decide what action to take. If we only supported the built-in actions, that would be easy, but the user-definable actions that invoke Tcl scripts make things a little more complicated. You need to check for those specially, before processing built-in actions. Here's a simplified example of an action method that could be used for the application frame that contains the quick menu:
public boolean
action(Event evt, Object arg)
{
// For simplicity, assume that the "tcl" package is imported
// Handle TclScriptMenuItems specially ...
if (evt.target instanceof TclScriptMenuItem) {
Result result = new Result();
// Assume we have the Tcl interpreter available as "tcl",
// and use it to invoke the script ...
int code = tcl.eval(evt.target.getScript(), result);
if (code != Interp.TCL_OK) {
// code to indicate that the script had an error
}
}
else if ("Align right".equals(arg)) {
// code to process "Align right" action
}
else if ("Bold".equals(arg)) {
// code to process "Bold" action
}
else if ("Insert Date".equals(arg)) {
// code to process "Insert Date" action
}
// ... and so on for all valid actions
else {
// code to signal an invalid action
}
}
Note |
You may have noticed that the sample action method will be very inefficient if there are a lot of actions. Because this chapter is not really about menu and event processing, I wanted to keep the code simple and obvious. For the curious, here's one idea for improving the efficiency of the method. For the quickmenu command, we have to prepare a hash table of valid actions. The command doesn't use the values in the hash table, however; it uses only the keys. Therefore, we can make use of the hash table, too. For each valid action, we can define a static final int code in some appropriate class (let's assume the class is called SpiffyApp) and store the matching integer code in the hash table along with the action label: actions.put("Align right", Then, instead of comparing the event argument against all the valid actions until we get a match, we can just switch on the value in the hash table: Integer action_code = actions.get(arg); switch (action_code.intValue()) { This approach is much more efficient. If we choose to go this route, however, we should make one more change and give the QuickMenuCommand instance a copy of our hash table rather than the original. This will protect us in case a future change to QuickMenuCommand requires that it change its hash table: tcl.createCommand("quickmenu", |
Now that the application can handle our menu items correctly, we're finally finished adding a user-definable menu facility to our application. Well, almost. There's just one more thing.
When I presented the first small example using the echo command, I mentioned that there were two ways in which it was unrealistic: The command didn't really have anything to do with the special functionality of the application, and there was no way for users to supply custom Tcl code. Everything we've done so far for the quickmenu facility has addressed the first problem. All of that is worthless unless the user's definition file actually gets loaded. Fortunately, solving the second problem is much easier. Executing the file is not difficult at all, but finding it can be a little complicated, and the means by which you find the file depends on the particular application and its target market, among other things. You may have a conventional filename that you use, or you may allow one to be specified as an application parameter. You may also want to support two definition files: a site-wide file and a user-specific file.
Here's one way to do it, based on a conventional filename in the user's home directory. System properties are used to locate the home directory:
// Assume that tcl.* and java.io.* are imported,
// and that the Tcl interpreter is available as "tcl"
// Find file "spiffymenu.tcl" in the user's home directory
File menufile = new File(System.getProperty("user.home"), "spiffymenu.tcl");
if (menufile.canRead()) { // If it's readable, load it
Result result = new Result();
// The Tcl command "source" evaluates the Tcl code in a file:
int code = tcl.eval("source " + menufile.getPath(), result);
if (code != Interp.TCL_OK) {
// code to indicate that the script had an error
}
}
Now we really are done. Any of our application's users can create a file called spiffymenu.tcl in their home directory and put Tcl code in that file to define their own menus, and their special menus will be created each time they run the application.
When you read the code in fragments like this, in a book, it seems like a lot of work. To put it into perspective, it has taken well under 300 lines of code (including blank lines and comments) to add this facility to the hypothetical application. There might, of course, be a few more complications in a real application (in particular, I've glossed over certain error situations with comments), but it's still a large benefit for a small cost.
Even the small amount of configurability afforded by the quickmenu command will be useful to many, but it's very simple and restricted; more general scripting facilities would be even better. There are several ways to build on what's been shown so far to provide even more useful facilities. Although the example was presented in the context of a word processing program, these ideas are useful for many different kinds of applications.
The quickmenu facility enables users to place commonly used actions onto an easily accessed menu, and those actions can be user-defined composite actions. Because of this focus on menus, actions aren't allowed to take parameters. Many more useful things would be possible in both the user menu and the user-defined actions if operations like these were available:
The facility also would be much more useful if it weren't so focused on actions. It's difficult for programs (including scripts) to do useful and intelligent things if they can't learn about their environment. Tcl scripts should be allowed to make queries to learn important things about the application's state. In the word processor example, possibilities include the following:
There are many other pieces of information that would be useful to scripts. The ability to learn such information would permit user-defined actions to make complicated decisions, automating frequent (but user-specific) operations.
Of course, once you start providing full-fledged extension facilities like these, you're providing much more than a menu definition facility, so the command name quickmenu and the filename spiffymenu.tcl aren't really appropriate any more. A slightly different structure should be used instead.
This chapter uses a menu definition facility, rather than a toolbar definition facility, so that the AWT-related code won't overwhelm the Tcl-related code in the example, but a toolbar facility would probably be more useful for many applications.
In addition to convenience scripting for users, Tcl is suitable for other roles in an application, some of them extremely important.
Elsewhere in this book, we talk about the importance of allowing users (or at least site administrators) to configure the security rules of an application (see Part 6, "Security"). This is a situation where it is particularly useful to use a scripting language, for several reasons:
The security configuration facility could provide simple rules to decide when particular types of access should be allowed or denied. For complicated cases, Tcl scripts could be invoked to make the decisions. Here is one simple possibility for the style of a Tcl-based security configuration file:
# This is Tcl, not Java!
# allow my company's applets to read my calendar and work diary files
allow read {calendar diary} {www.myco.com intranet.myco.com}
# also allow my company's internal applets to connect to any host
allow connect * {intranet.myco.com}
# allow an applet to execute a couple of harmless system commands.
# be careful here!!!
allow exec -script checkexec
proc checkexec {commandstring} {
if {[string match "date *" commandstring]
|| [string match "cd .." commandstring]} {
return allow
} else {
return deny
}
}
Naturally, a security configuration language should be designed with a lot of care, and this example is not really appropriate for the task. A reusable scripting language would make a good basis for such a facility, however.
User interfaces need to be flexible, and a scripting language might be a good way to provide some of that flexibility, allowing users to adjust default colors, fonts, and sizes to fit their own preferences, unusual hardware, or special needs. (Color-blind users, for example, sometimes find the capability of changing default colors particularly important.) Providing even more interface flexibility through a scripting language may prove to be a benefit to the application developers as well, making it easier to adjust the interface in response to user feedback, or even permitting more radical changes, such as adapting a graphical interface for the visually impaired.
Many application developers go so far as to implement most of their applications in a scripting language, using a lower-level language such as Java or C only for the small portions of the applications that might otherwise be very slow or complicated or that have low-level system dependencies. Such a strategy can be extremely fruitful for a developer, and the resulting application will also be very powerful and flexible for users-perhaps too flexible. When user scripts can override or modify application internals, they can introduce their own bugs into the center of the application, greatly complicating the job of supporting the application. Tcl's security features can be used to solve that problem. User scripts can be executed in a secure environment where they can access only application commands that have been explicitly exported for users, rather than having access to application internals.
Because many scripting languages deal easily with strings, they make good "glue" languages: They can be used to connect two applications in certain situations, even if those applications are written in different languages. Communication via strings is important for building large systems out of smaller systems, as the builders of the Internet have learned. (Human-readable, string-oriented protocols are preferred over binary protocols when efficiency isn't a primary concern, because they are easier to build and debug.)
In short, if you wish to make your application flexible, configurable, customizable, extensible, or useful in unanticipated contexts, either for your users' benefit or your own, integrating a scripting language is the way to go.
Tcl is extensible primarily so that applications can add specialized functionality, but many general-purpose extensions have been written as well. Recent versions of Tcl support dynamic loading of extension packages, making them convenient to use. This section describes a few of the most popular and useful Tcl extensions. Many of them currently run only on UNIX systems, where Tcl originated, but some do run on other platforms, and more will soon. You might find some of these packages useful; additionally, there are many more Tcl extensions besides those mentioned here.
Tk is the most important Tcl extension. It was also designed and implemented by John Ousterhout, and many Tcl users were first drawn to Tcl because of the availability of Tk. Tk is a graphical user interface toolkit that provides a rich suite of user interface widgets, including general-purpose text and graphics widgets that have far more functionality than the Java equivalents currently do. Tk is also extensible, so new widgets can be provided, and many extension widgets have been written. Quite a few commercial applications have been built using Tk, including hospital administration applications and the UNIX version of the game SimCity. Tk was recently ported to work on Macintosh and Windows systems, and it strives for uniformity by supplying missing capabilities on some platforms (as opposed to the AWT's current least-common-denominator approach). Tk currently provides the UNIX Motif user interface style on all platforms, but work is underway to support the native look and feel for Windows and Macintosh systems. More information about Tk is available on the Tcl/Tk home page:
http://www.smli.com/research/tcl/
Several graphical user interface builders for Tk are either currently available or in development. One called SpecTcl (by Stephen Uhler of Sun) is particularly easy to use, and it has been adapted to generate Java code for AWT interfaces as well.
Extended Tcl (also known as TclX) deserves mention because it has been influential in the development of Tcl itself; many of the facilities now in the Tcl core first appeared in TclX. This package extends Tcl with many conveniences and higher-level facilities, and it also provides access to some low-level UNIX facilities. It is available at the following address:
ftp://ftp.neosoft.com/pub/tcl/tclx-distrib/
Tix consists of a library of C routines that make it easier to build new Tk widgets and a large suite of widgets built using that library. The Tix package includes Windows-style comboboxes, tabbed notebooks, and common dialogs (such as file selection dialogs). Tix is currently being ported to Windows systems. It was originally written by Ioi Kim Lam, and it is now a supported product from Expert Interface Technologies. For more information, check out this address:
http://www.xpi.com/tix/
[incr Tcl] is an object-oriented Tcl extension by Michael McLennan of Bell Laboratories (the unusual name is a Tcl pun on the name C++). It provides namespaces, classes, inheritance, and the capability of composing Tk widgets into "megawidgets." More information is available at the following address:
http://www.tcltk.com/itcl/
A group at DSC Technologies uses [incr Tcl], and they have built another library of widgets using that package. They call their package [incr Widgets], and it is available from this address:
http://www.tcltk.com/iwidgets/
Tcl-DP is a package for distributed programming using Tcl. It provides a Tcl-based remote procedure call, a high-level distributed object facility, and a service location server. Tcl-DP is available at this address:
ftp://mm-ftp.cs.berkeley.edu/pub/multimedia/Tcl-DP/
The Continuous Media Toolkit is a Tk extension for building multimedia applications, especially using audio and video. One fascinating aspect of this package is the support of Tcl scripts as a media type, used for controlling and synchronizing other media streams. The home page is at the following address:
http://www.bmrc.berkeley.edu/cmt/
George Howlett from Bell Laboratories has written a package of Tk widgets he calls BLT. It includes table and graph widgets, and it is available from the following address:
ftp://ftp.neosoft.com/pub/tcl/NEW/BLT2.1.tar.gz
Expect is a Tcl extension by Don Libes, designed for controlling interactive, character-based applications. It can be used to write test suites for such applications or to automate complicated interactions. It also is useful in conjunction with Tk to build graphical interfaces to existing character applications. For more information, see this page:
http://elib.cme.nist.gov:80/pub/expect/
GroupKit is a groupware toolkit based on Tcl and Tk. It comes with many example groupware applications, including "electronic spaces" with group awareness, as well as collaborative editors, drawing tools, and meeting tools. GroupKit information is at this address:
http://www.cpsc.ucalgary.ca/projects/grouplab/groupkit/
The Visualization toolkit is a 3D graphics and scientific visualization toolkit written in C++ with an interface to Tcl and Tk. Here is Vtk's home page:
http://www.cs.rpi.edu/~martink/
Most applications, even small ones, need some sort of customization or configuration language. As applications mature, the demands placed on such scripting languages can grow far beyond what the developers anticipated. Several scripting languages, most notably Tcl, were designed to make it easy for developers to include a full-featured scripting language in their applications from the very beginning.
Scripting languages are just as useful for Java applications as they are for C and C++ applications, and an interface between Java and Tcl has been developed. Tcl can be used to make Java applications much more versatile and flexible for the benefit of users, administrators, and developers.