Welcome to the Core Java Technologies Tech Tips, May 20, 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:
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. DRAWING DASHED LINES WITH
|
Stroke drawingStroke = ...;
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(drawingStroke);
// draw
}
|
If you can spare the memory, you can create the Stroke outside the paint method. That way, you don't have to create the same object with each drawing pass.
To demonstrate, the following program draws a rectangle with a five-pixel-wide line.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
public class Stroke1 extends JFrame {
Stroke drawingStroke = new BasicStroke(5);
Rectangle2D rect =
new Rectangle2D.Double(20, 40, 100, 40);
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setStroke(drawingStroke);
g2d.draw(rect);
}
public static void main(String args[]) {
JFrame frame = new Stroke1();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setSize(200, 100);
frame.show();
}
}
|

In the Stroke1 example, the BasicStroke constructor was passed a width of 5. Technically, the argument is a float, but the compiler is smart enough to upcast the argument. This constructor is one five constructors available for BasicStroke:
BasicStroke()BasicStroke(float width)BasicStroke(float width, int cap, int join)BasicStroke(float width, int cap, int join, float miterlimit)BasicStroke(float width, int cap, int join, float miterlimit, float[] dash, float dash_phase)
You can think of the first constructor as the "old" style, that is, it's used to draw a single-pixel-wide solid line. You've already seen the second constructor in the Stroke1 example. The last three constructors require you to understand the settings for cap and segment joining. When a path is unclosed, then a cap is used on both of the ends of the path. If the path is explicitly closed, then segment joining is used between the last segment and the first.
There are three constants in the BasicStroke class for end cap settings: CAP_BUTT, CAP_ROUND, and CAP_SQUARE. These are used at the beginning and end of any unclosed paths, as well as at the ends of dash segments. Unclosed paths are those that aren't explicitly closed. For instance, a Polygon always closes its path, whereas a GeneralPath does not (unless you explicitly call its closePath method on a given subpath). The default setting, CAP_SQUARE, is used when a constructor doesn't offer an end cap argument.
CAP_BUTT: Offers dash segments with no decorations.CAP_ROUND: Offers dash segments with rounded ends.CAP_SQUARE: Offers dash segments with squared-off ends.
For both CAP_ROUND and CAP_SQUARE, the decorations extend beyond the original segment end by a distance equal to half the width of the line stroke.
Here's what each of the end caps look like:

There are another three constants in the BasicStroke class for the segment connection settings: JOIN_BEVEL, JOIN_MITER, and JOIN_ROUND. These define how a corner looks where two path segments connect, such as for the corners of a rectangle (although the angle doesn't have to be 90 degrees to be a connection).
JOIN_BEVEL: Joins line segments by directly connecting outside edges.JOIN_MITER: Joins line segments by connecting outside edges, however, instead of directly connecting the two lines like JOIN_BEVEL, it extends the segments from the outside edge until the segments meet.JOIN_ROUND: Joins line segments with a rounded corner.
Here's what each of the segment connections look like:

Now let's look at the remaining BasicStroke constructor options: miterlimit, dash[], and dash_phase.
miterlimit: Specifies the miter limit to use when the join style is JOIN_MITER. This means how far the outside edges can extend before a join is possible. The miter limit affects the join style: if the length of a miter is greater than the miter limit (default of 10) multiplied by half the stroke width, then a JOIN_BEVEL is used instead of a JOIN_MITER.dash[]: An array of float values to use as repeating dash lengths and clear sections. Use only one value in the array if all the dashes and all the clear sections have the same length. For different lengths, have an array of the different sizes. For instance, the array new float[] {5.0f} produces a repeating five-pixel dash, while the array new float[] {5.0f, 10.0f} produces five-pixel dashes followed by ten empty pixels. The array acts in a circular fashion, wrapping back to the beginning if the array if more drawing space is available than the combined path length.dash_phase: Acts as an offset into dashes. For instance, if dash_phase is zero, the first dash starts at the beginning of the coordinate space from the drawing origin. When dash_phase is not zero, the dash starts the specified number of pixels into the dash. This means that for a ten-pixel dash and a dash_phase of 4, you only see six drawing pixels for the first dash.
The following program demonstrates the different JOIN_* constants. The program uses dashed lines to draw a figure that looks like a "Star Trek: The Next Generation" communicator. Adjust the CAP_* constants, dash[], miter limit, and phase to see what changes to those settings do to the output. Notice especially what happens in the figure to the bottom corners of the polygon.
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
public class Stroke2 extends JFrame {
Stroke drawingStroke1 =
new BasicStroke(
10,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_BEVEL,
0,
new float[] {9},
0
);
Stroke drawingStroke2 =
new BasicStroke(
10,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
50,
new float[] {9},
0
);
Stroke drawingStroke3 =
new BasicStroke(
10,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND,
0,
new float[] {9},
0
);
GeneralPath shape;
public Stroke2() {
shape = new GeneralPath();
shape.moveTo(50, 0);
shape.lineTo(100, 100);
shape.lineTo(50, 50);
shape.lineTo(0, 100);
shape.lineTo(50, 0);
}
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.translate(50, 50);
g2d.setStroke(drawingStroke1);
g2d.draw(shape);
g2d.translate(0, 100);
g2d.setStroke(drawingStroke2);
g2d.draw(shape);
g2d.translate(0, 100);
g2d.setStroke(drawingStroke3);
g2d.draw(shape);
}
public static void main(String args[]) {
JFrame frame = new Stroke2();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
frame.setSize(200, 400);
frame.show();
}
}
|

For additional information on the Java 2D API, see the Java 2D documentation and the Displaying Graphics with Graphis2D trail of the Sun Java Tutorial.
DOCLETSPutting comments in code is good programming practice, but something that is often overlooked. To help with this lack of documentation, the early designers of the Java programming language devised a plan for self-documenting code. They devised a little tool called javadoc that you could run against a set of classes, and by doing so, generate documentation for those classes.
Running the javadoc tool generates a series of HTML pages that lists class names with their set of methods and fields. This result is similar to what developers produced prior to the existence of the Java programming language by running scripts, typically in Perl, against a series of source files.
The javadoc tool can also provide more extensive commenting. What the designers added for the Java programming language, was a special form of comment called a javadoc comment. When a javadoc comment is included in a class definition, it is also included in the generated output when that class is passed through the javadoc tool.
The Java programming language provides for single-line comments or multi-line comments. Single-line comments start with // and go to end of the line. Multi-line comments start with /* and go to */. While multi-line comments could be on a single line, the end-of-line doesn't signal the end of the comment. The first */ after the opening /* indicates the end.
The javadoc comment extends the multi-line comment. Instead of starting with a slash (/) followed by one star (*), the javadoc comment begins with a slash followed by two stars. Everything between the starting /** tag and the end */ tag is the comment.
So, if a class definition is as follows:
/**
* This is a javadoc comment
*/
public class Comment {
/**
* So is this
*/
public Comment() {
}
}
|
and you run the javadoc command as follows:
javadoc Comment.java
you get several support files and one file named Comment.html. The Comment.html file provides the documentation for the class. This file would include the "This is a javadoc comment" as the text for the class definition and "So is this" with the constructor.
This, in itself is useful, but the javadoc tool is more flexible than that. If you don't like the default output of the javadoc command, you can change it. By creating a custom doclet, you can customize the output format. The format could be something as simple as a text dump of the classes found by the tool, or something as complicated as a FrameMaker document or a comment checker. If you have a PDF file generator available, or if you just want to convert the FrameMaker output to PDF, you could even generate javadoc output as PDF.
You'll find the doclet classes in the com.sun.javadoc package, in the lib/tools.jar file. That means they are found in the J2SE development kit, but not in the standard Java runtime environment.
Here are the steps to create a simple doclet, one that prints class signatures. The signatures include the access modifiers, name, interfaces implemented, and extended class.
As you follow the steps it's important to understand that a doclet is run from the javadoc command, not the java command. This equates to no main method. Another thing to note is that a doclet extends Doclet.
So let's start with the following framework:
import com.sun.javadoc.*;
public class DocIt extends Doclet {
public static boolean start(RootDoc root) {
// Do work
return true;
}
}
|
The start method returns true to indicate success, false otherwise. The RootDoc passed to the start method holds the information from one run of javadoc.
For the "Do work" part of the method, you can literally do whatever you want, as long as it is valid code. Typically, you would get all the classes (and interfaces) to be documented, and loop through each to do whatever you wanted to do for that one. You do this using the classes method of the RootDoc, with the help of a for-loop:
public static boolean start(RootDoc root) {
ClassDoc[] classes = root.classes();
for (int i = 0; i < classes.length; ++i) {
ClassDoc cd = classes[i];
// Do work for specific ClassDoc
}
return true;
}
|
Imagine a simple "Hello, World" doclet. That sort of doclet would likely just print the class name. However, a more sophisticated doclet might print the whole class signature. In order to do that, you use the ClassDoc interface. This interface extends the ProgramElementDoc and Type interfaces (which in turn extend Doc and Comparable). The four interfaces together give you a way to find out quite a bit about a class. Specifically, they allow you to take advantage of the following methods:
modifiers: Generates a string, combining all the modifiers.isClass: Finds out if something is a class or interface.qualifiedName: Gets the fully qualified name for a class or interface.typeName: Returns an unqualified name.interfaces: Provides an array of ClassDoc objects, one for each interface implemented by the class.superclass: Identifies the class that this class extends.
Putting all this together, gives you the following method. The method would be called for each ClassDoc in start.
static void printSignature(ClassDoc cd) {
System.out.print(cd.modifiers());
System.out.print(
cd.isClass() ? " class " : " interface ");
System.out.println(" " + cd.qualifiedName());
ClassDoc interfaces[] = cd.interfaces();
for (int i=0, n=interfaces.length; i<n; i++) {
System.out.println(" implements " +
interfaces[i].qualifiedName());
}
ClassDoc parent = cd.superclass();
if (parent != null) {
System.out.println(" extends " +
parent.qualifiedName());
}
}
|
For the DocIt class, printSignature would print the following output:
public class DocIt
extends com.sun.javadoc.Doclet
At this point you need to compile the class. You need to use the -classpath option when you compile. That's because the tools.jar file where the Doclet and related classes are located is outside the default classpath. To compile the command, issue the following command (although shown on two lines, the command needs to go on one line).
In Windows:
javac -classpath
c:\j2se1.4.1\lib\tools.jar DocIt.java
In Unix:
javac -classpath
/homedir/jdk14/j2sdk1.4.1/lib/tools.jar DocIt.java
Then, to run it, use the javadoc command. You need to specify the -doclet option to identify the doclet to run. You also need to specify the -docletpath option to tell javadoc where to find the doclet. You don't have to specify the tools.jar file in the classpath when you run the doclet. However, if you want to run the doclet on itself, you should include tools.jar in the classpath. That way, the doclet will find the com.sun.javadoc classes, and can fully specify them as coming from that package. So, to run the DocIt doclet on itself, issue the following command (on one line).
In Windows:
javadoc -doclet DocIt -docletpath .
-classpath c:\jdk1.4.1\lib\tools.jar DocIt.java
In Unix:
javadoc -doclet DocIt -docletpath .
-classpath /homedir/jdk14/j2sdk1.4.1/lib/tools.jar
DocIt.java
And, as expected, you get output:
public class DocIt
extends com.sun.javadoc.Doclet
To run the doclet against more classes, simply include more classes on the command line.
You can use doclets to do more than print the class signature. A doclet can print information about the constructors, fields, and methods of the class. There is a xxxDoc interface available for each type of information: ConstructorDoc for constructors, FieldDoc for fields, and MethodDoc for methods. And because the xxxDoc classes implement Comparable, the information they produce is directly sortable.
Here's an example that demonstrates the power of doclets. The following DocIt class combines the printSignature method introduced earlier, with similar methods that print the name, constructors, fields, and methods, respectively. The output of DocIt is similar to the output produced by the javap command (the Java disassembler), although the output format is slightly different. The DocIt output includes headers for each type of output, and no semicolons at the end of lines.
import com.sun.javadoc.*;
import java.util.*;
public class DocIt extends Doclet {
public static boolean start(RootDoc root) {
ClassDoc[] classes = root.classes();
for (int i = 0; i < classes.length; ++i) {
ClassDoc cd = classes[i];
printName(cd);
printSignature(cd);
printConstructors(cd.constructors());
printFields(cd.fields());
printMethods(cd.methods());
}
return true;
}
static void printName(ClassDoc cd) {
System.out.println(cd.typeName());
}
static void printSignature(ClassDoc cd) {
System.out.print(cd.modifiers());
System.out.print(
cd.isClass() ? " class " : " interface ");
System.out.println(cd.qualifiedName());
ClassDoc interfaces[] = cd.interfaces();
for (int i=0, n=interfaces.length; i<n; i++) {
System.out.println(" implements "
+ interfaces[i].qualifiedName());
}
ClassDoc parent = cd.superclass();
if (parent != null) {
System.out.println(" extends " +
parent.qualifiedName());
}
}
static void printConstructors(
ConstructorDoc cons[]) {
if (cons.length > 0) {
System.out.println("Constructors");
Arrays.sort(cons);
}
for (int i=0, n=cons.length; i<n; i++) {
System.out.print(cons[i].modifiers());
System.out.print(" " + cons[i].name());
System.out.println(
cons[i].flatSignature());
}
}
static void printFields(FieldDoc fields[]) {
if (fields.length > 0) {
System.out.println("Fields");
Arrays.sort(fields);
}
for (int i=0, n=fields.length; i<n; i++) {
System.out.print(fields[i].modifiers());
Type type = fields[i].type();
System.out.print(" " +
type.qualifiedTypeName());
System.out.println(" " + fields[i].name());
}
}
static void printMethods(MethodDoc methods[]) {
if (methods.length > 0) {
System.out.println("Methods");
Arrays.sort(methods);
}
for (int i=0, n=methods.length; i<n; i++) {
System.out.print(methods[i].modifiers());
Type type = methods[i].returnType();
System.out.print(" " +
type.qualifiedTypeName());
System.out.print(" " + methods[i].name());
System.out.println(
methods[i].flatSignature());
}
System.out.println();
}
}
|
For example, if you compile DocIt (remember to specify the -classpath option when you compile) and then run it against the Stroke2.java class described in the first tip in this issue "Drawing Dashed Lines with Stroke", you should see the following:
Loading source file Stroke2.java...
Constructing Javadoc information...
Stroke2
public class Stroke2
extends javax.swing.JFrame
Constructors
public Stroke2()
Methods
public static void main(String[])
public void paint(Graphics)
|
There is much more that could be done here to print fancier output or do some of the behavior yourself. For instance, the flatSignature method in the interface ExecutableMemberDoc combines all the parameters to the method or constructor for you. You could get the Parameter array from a ConstructorDoc or MethodDoc through parameters, and generate that output yourself. You can also add the Throws tag to print the exceptions that can be thrown. And, if you want to include those javadoc comments in the output, the commentText and setRawCommentText methods of the Doc class come in handy.
If you're interested in generating doclets from within a Java program, try out the execute method of the com.sun.tools.javadoc.Main class.
For more information about the javadoc tool and the API for doclets, see the resources listed on the Javadoc 1.4 Tool page.
|
| ||||||||||||