Sun Java Solaris Communities My SDN Account Join SDN
 
Technical Articles and Tips

Generating Custom Taglets and Compiling Source Directly From a Program

 
View this issue as simple text July 22, 2003    

Welcome to the Core Java Technologies Tech Tips, July 22, 2003. Here you'll get tips on using core Java technologies and APIs, such as those in Java 2 Platform, Standard Edition (J2SE).

This issue covers:

-Generating Custom Taglets
-Compiling Source Directly From a Program

These tips were developed using Java 2 SDK, Standard Edition, v 1.4.

This issue of the Core Java Technologies Tech Tips is written by John Zukowski, president of JZ Ventures, Inc.

See the Subscribe/Unsubscribe note at the end of this newsletter to subscribe to Tech Tips that focus on technologies and products in other Java platforms.

.
.

GENERATING CUSTOM TAGLETS

The May 20, 2003 issue of Core Java Technologies Tech Tips described how to generate custom doclets for use with the javadoc tool. The tip below expands on the earlier tip -- it shows how to extend javadoc by creating custom taglets.

When you write javadoc comments for classes and methods, you can include special keywords preceded by an "at" sign (@). For instance, @return allows you to describe what a method is supposed to return. When the javadoc tool sees an @ command, it adds the necessary text to the generated output. "@return" is an example of a javadoc tag, one of 17 predefined javadoc tags in J2SE 1.4. See the javadoc tags section of the javadoc reference guide for the complete list.

Although there are a set of predefined javadoc tags, they might not meet all your needs. Fortunately, you have the ability to define custom tags for use with the standard doclet. For example, if you want to keep a log of things to complete in the source code, you could create a custom tag named @todo. You could use the tag as a reminder of things you need to add to the code. When you run the javadoc tool, the @todo tag could generate highlighted text -- perhaps in a large font or with a special background color. Suppose too that you want to include a real working example in javadoc comments, but you don't want to actually code the example in the javadoc comments. In this case, you might create an @example tag that "includes" the working example class definition into the javadoc comments. You could then compare the source code to the working example to ensure that the source "looks right". Also, because the source file is separately compileable, you can ensure that it successfully compiles and runs.

Use the Taglet API to create your own custom tag. You can find it in the com.sun.tools.doclets package. The Taglet interface consists of a series of methods that allow you to say where the tag is valid and how to generate a string representation for the tag contents. (There is also a method for getting a tag's name.) Here are the methods:

   public boolean inField()
   public boolean inConstructor()
   public boolean inMethod()
   public boolean inOverview()
   public boolean inPackage()
   public boolean inType()
   public boolean isInlineTag()
   public java.lang.String getName()
   public java.lang.String toString(
                               com.sun.javadoc.Tag tag)
   public java.lang.String toString(
                            com.sun.javadoc.Tag[] tags)

Implementations of the Taglet interface must also provide a registration method:

   public static void register(Map tagletMap)

The registration method adds an instance of the custom taglet to the map, with its name as the key.

Note that you can override taglets, for instance, you can replace a taglet provided by the system. You should delete any overridden taglet before you add one. To avoid a name conflict, you should name your taglets similar to your packages, using the reverse DNS ordering for your domain. This means naming a taglet com.example.MyTaglet if your domain is example.com.

Before looking at the interface, there is one other concept that needs to be explained: block vs. inline tags. You can think of a block tag as one that stands alone. It must appear at the beginning of a line (ignoring asterisks and white space), and goes on to either the next block tag or the end of the javadoc comment. By comparison, inline tags are specified within {} characters, and can exist within any javadoc comments (including, but not limited to, block tags). For example, the following inline @link tag generates a link to the yourMethod method in the myMethod description.

   public class Example1 {
     /**
      * Your method is at {@link #yourMethod()}
      */
     public void myMethod() {
     }
     public void yourMethod() {
     }
   }

Now let's examine the actual interface. All the inXXX methods in the interface allow you to control where the tag is valid. For example, if a tag is valid only in the constructor, then inConstructor returns true and all the other methods return false. The toString methods return the generated string. If multiple block tags for the same taglet are present, the array version of toString is used.

At this point you might wonder how to access the text after an@taglet. The answer is through the text() method of the Tag interface. See the description of the Tag interface for information about text() and other Tag interface methods.

Let's demonstrate creating a taglet. The program that follows creates an @javapedia block tag. The tag allows someone to specify a keyword that automatically links into the Javapedia. For example, the "@javapedia Struts" tag is converted to the link http://wiki.java.net/bin/view/Javapedia/Struts/. If multiple @javapedia tags are present, each gets its own link. Note, there is no validation that a definition exists for the keyword in the Javapedia. This is left for the writer of the javadoc comment to verify before including the link in the javadoc comments.

   import com.sun.tools.doclets.Taglet;
   import com.sun.javadoc.*;
   import java.util.Map;

   public class JavapediaTaglet implements Taglet {

     private static final String NAME = "javapedia";
     private static final String HEADER = "Javapedia:";
  
   public String getName() {
     return NAME;
   }

   public boolean inField() {
     return true;
   }

   public boolean inConstructor() {
     return true;
   }

   public boolean inMethod() {
     return true;
   }

   public boolean inOverview() {
     return true;
   }

   public boolean inPackage() {
     return true;
   }

   public boolean inType() {
     return true;
   }

   public boolean isInlineTag() {
     return false;
   }

   public static void register(Map tagletMap) {
    Taglet tag = new JavapediaTaglet();
      tagletMap.put(tag.getName(), tag);
   }

   public String toString(Tag tag) {
       return "<DT><B>" + HEADER + "</B><DD>"
         + "<a href=\"http://wiki.java.net/bin/view/Javapedia/"
         + tag.text()
         + "\">" 
         + tag.text()
         + "</a>" 
         + "</DD>\n";
   }

   public String toString(Tag[] tags) {
     if (tags.length == 0) {
       return null;
     }
     String result = "\n<DT><B>" + HEADER + "</B><DD>";
     for (int i = 0; i < tags.length; i++) {
       if (i > 0) {
         result += "</DD><DD>";
       }
       result += "<a href=\"http://wiki.java.net/bin/view/Javapedia/"
       + tags[i].text()
       + "\">" 
       + tags[i].text()
       + "</a>";
     }
     return result + "</DD>\n";
   }
 }

After you've written the Taglet, you need to compile it. Similar to the way you create a custom doclet, you need to use the -classpath option when you compile. That's because the tools.jar file where the Taglet interface and related classes are located is outside the default classpath. To compile the taglet, issue the following command (although shown on two lines, the command needs to go on one line).

In Windows:

    javac -classpath 
        c:\j2se1.4.2\lib\tools.jar JavapediaTaglet.java

In Unix:

    javac -classpath 
        /homedir/jdk14/j2sdk1.4.2/lib/tools.jar JavapediaTaglet.java

Replace homedir with your actual home directory.

Then, to use the taglet when running javadoc, you need to specify the -taglet option. Also, you might need to specify the -tagletpath option to tell javadoc where to find the taglet. You don't have to specify the tools.jar file in the classpath when you run the taglet.

   javadoc -taglet JavapediaTaglet Example2.java

Just be sure to include the @javapedia tag in your example source.Otherwise, the new taglet code won't don't anything. Here is the example source.

   public class Example2 {
     /**
      * Your method is at {@link #yourMethod()}
      * @javapedia Struts
      * @javapedia MVC
      */
     public void myMethod() {
     }

     /**
      * @javapedia MVC
      */
     public void yourMethod() {
     }
   }

After you've run javadoc, open the generated HTML file (you can start with index.html) to see the new taglet in action.

For more information about the javadoc tool and the API for taglets, see the resources listed on the Javadoc 1.4 Tool page.

.
.

COMPILING SOURCE DIRECTLY FROM A PROGRAM

Imagine being able to compile code directly from a Java application without the need to issue a javac command. This would allow you to do things like enter code into a JTextArea and have it compiled simply by pressing a button. In fact, you have this capability. By putting the tools.jar file in your classpath, you can compile directly from a Java application.

The command line-based javac compiler is simply a wrapper to the com.sun.tools.javac.Main class. Using the class's static compile method, you can pass (in a String[]) the name of one or more files to compile, and you don't need to use the command line for the compilation.

To demonstrate, this tip presents a program that displays a text field, a text area, and a button. You enter the name of a program (that is, the one you want to compile) in the text field, the program's source code in the text area, and then click the button. The source code is compiled and if the compile is successful, the program runs. Note however that the API demonstrated here is unsupported and dependent on an installation similar to the one shipped by Sun. Apple's, for example, is organized differently. With the 1.5 (Tiger) release of J2SE, the introduction of a portable API is planned.

Here is the demonstration program, RunIt:

   import java.awt.*;
   import java.awt.event.*;
   import javax.swing.*;
   import java.io.*;
   import java.lang.reflect.*;

   public class RunIt extends JFrame {
     JPanel contentPane;
     JScrollPane jScrollPane1 = new JScrollPane();
     JTextArea source = new JTextArea();
     JPanel jPanel1 = new JPanel();
     JLabel classNameLabel = new JLabel("Class Name");
     GridLayout gridLayout1 = new GridLayout(2,1);
     JTextField className = new JTextField();
        JButton compile = new JButton("Go");
  Font boldFont = new java.awt.Font("SansSerif", 1, 11);

     public RunIt() {
       super("Editor");
       setDefaultCloseOperation(EXIT_ON_CLOSE);
       contentPane = (JPanel) this.getContentPane();
       this.setSize(400, 300);
       classNameLabel.setFont(boldFont);
       jPanel1.setLayout(gridLayout1);
       compile.setFont(boldFont);
       compile.setForeground(Color.black);
       compile.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) {
           try {
             doCompile();
           } catch (Exception ex) {
             System.err.println(
                   "Error during save/compile: " + ex);
             ex.printStackTrace();
           }
         }
       });  
       contentPane.add(jScrollPane1, BorderLayout.CENTER);
       contentPane.add(jPanel1, BorderLayout.NORTH);
       jPanel1.add(classNameLabel);
       jPanel1.add(className);
       jScrollPane1.getViewport().add(source);
       contentPane.add(compile, BorderLayout.SOUTH);
     }
     public static void main(String[] args) {
       Frame frame = new RunIt();
       // Center screen
       Dimension screenSize =
         Toolkit.getDefaultToolkit().getScreenSize();
       Dimension frameSize = frame.getSize();
       if (frameSize.height > screenSize.height) {
         frameSize.height = screenSize.height;
       }
       if (frameSize.width > screenSize.width) {
         frameSize.width = screenSize.width;
       }
       frame.setLocation(
         (screenSize.width - frameSize.width) / 2,
         (screenSize.height - frameSize.height) / 2);
       frame.show();
     }
     private void doCompile() throws Exception {
       // write source to file
       String sourceFile = className.getText() + ".java";
       FileWriter fw = new FileWriter(sourceFile);
       fw.write(source.getText());
       fw.close();
       // compile it
       int compileReturnCode =
         com.sun.tools.javac.Main.compile(
             new String[] {sourceFile});
       if (compileReturnCode == 0) {
         // run it
         
         Object objectParameters[] = {new String[]{}};
         Class classParameters[] = 
                     {objectParameters[0].getClass()};
         Class aClass = 
                   Class.forName(className.getText());
         Object instance = aClass.newInstance();
         Method theMethod = aClass.getDeclaredMethod(
                              "main", classParameters);
         theMethod.invoke(instance, objectParameters);
       }
     }
   }

Note that better exception handling could be done by the doCompile method. However, because this isn't an exercise in exception handling, exception handling improvements are left to the reader.

Most of the code in the RunIt program is in support of creating a graphical user interface. The part of the code that processes the entries into the GUI is the doCompile method. The method performs the following actions:

  1. Save the source to disk. The method saves the source in a file whose name is taken from the class name entered in the JTextField of the GUI.
        String sourceFile = className.getText() + ".java";
        FileWriter fw = new FileWriter(sourceFile);
        fw.write(source.getText());
        fw.close();
    
  2. Compile the source. This is where the com.sun.tools.javac.Main is used. The method creates a String[] for the source files (only one is used in the demonstration), and compiles them using Main's compile method. The method returns an error code of 0 if the compilation is successful, and non-zero if not.
        int compileReturnCode =
          com.sun.tools.javac.Main.compile(
              new String[] {sourceFile});
    
  3. As with any class, running involves calling the class's main method. So, reflection is used to find the appropriate method and invoke it.
        if (compileReturnCode == 0) {
          // run it
          Object objectParameters[] = {new String[]{}};
          Class classParameters[] = 
                           {objectParameters[0].getClass()};
          Class aClass = Class.forName(className.getText());
          Object instance = aClass.newInstance();
          Method theMethod = aClass.getDeclaredMethod(
                                   "main", classParameters);
          theMethod.invoke(instance, objectParameters);
        }
    

For more information on invoking methods with the Reflection API, see the Tech Tip "Using Reflection to Test Methods and Classes".

To compile the RunIt program, you need to add the tools.jar file to your classpath. This file contains the compiler classes. You can use the -classpath option when you compile. That's because the tools.jar file is outside the default classpath. To compile the program, issue the following command (although shown on two lines, the command needs to go on one line).

In Windows:

    javac -classpath 
        c:\j2se1.4.2\lib\tools.jar RunIt.java

In Unix:

    javac -classpath 
        /homedir/jdk14/j2sdk1.4.2/lib/tools.jar RunIt.java

Replace homedir with your actual home directory.

You also need to include the tools.jar file in your runtime classpath. That's because you want to use the compiler at runtime. To run the program, issue the following command (although the Unix command is shown on two lines, it needs to go on one line).

In Windows:

    java -classpath c:\jdk1.4.2\lib\tools.jar;. RunIt

In Unix:

   java -classpath 
      /homedir/jdk14/j2sdk1.4.1/lib/tools.jar:. RunIt

Notice the "." indicating the current directory in the classpath. New class files will be created by the compiler. The "." makes these class files accessible.

Running the program displays the GUI. Then you can:

  1. Enter the name of the class, such as Sample, to be compiled in the JTextField.

  2. Enter the source code in the JTextArea. Here's the source code for Sample:
        public class Sample {
          public static void main(String args[]) {
            System.out.println(new java.util.Date());
          }
        }
    

  3. Click the Go button.
Sample

Output is sent to the console. For example, Sample should produce output that looks something like this:

   Tue Jul 22 11:25:16 PDT 2003

RunIt allows you to create any class. if the program you enter is Swing-based, you would see a new window appear.

See the Java 2 SDK Tools and Utilities documentation for further information about Java 2 SDK tools and capabilities.

.
.

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Core Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn

Subscribe to other Java developer Tech Tips:

- Enterprise Java Technologies Tech Tips. Get tips on using enterprise Java technologies and APIs, such as those in the Java 2 Platform, Enterprise Edition (J2EE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Core Java Technologies Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html


Copyright 2003 Sun Microsystems, Inc. All rights reserved.
4150 Network Circle, Santa Clara, CA 95054 USA.


This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html


Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks (http://www.sun.com/suntrademarks/) of Sun Microsystems, Inc. in the United States and other countries.