Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

A Text UI for the Java AWT


Dr. Dobb's Journal September 1997: A Text UI for the Java AWT

Tomorrow's software for yesterday's terminals

Stuart, a software developer for Business Management Systems, can be contacted at [email protected].


The Java Abstract Window Toolkit (AWT) provides a user interface that is available on many different platforms without recompiling. Most programmers simply use the AWT implementation provided with the Java distribution. However, the java.awt package also provides java.awt.peer, a documented interface that lets you roll your own AWT.

In this article, I present a text user-interface (TUI) toolkit called TUIKit that was created with java.awt.peer. TUIKit consists of a pure Java server that runs anywhere, and a C++ client program that runs on UNIX servers. The main reason I developed the TUI look-and-feel for the Java AWT was to allow ASCII terminals to run Java applications on UNIX servers. In addition to implementing a TUI look-and-feel, TUIKit enables a multiuser Virtual Machine (VM). It is also a good starting point for creating a new AWT implementation because it is simple, yet complete. Finally, TUIKit solves Java's untrusted browser security problem.

Figure 1 is the freely available Teikada IDE environment for Java ([email protected] .pfu.fujitsu.co.jp) running a memory manager on the graphical screen. Figure 2, on the other hand, is the same memory manager running on a TUI screen. The memory monitor contains a canvas with a bar graph of memory usage. Because of the scaling, the fillRect() calls collide with the drawRect() calls and erase parts of the rectangles just drawn. Figures 1 and 2 are from the same Java binary on the same server.

The complete source code for the toolkit is available electronically (see "Availability," page 3). The code is in a Java package named bmsi.tui and the Toolkit is named bmsi.tui.TUIKit. There are no interpackage references, so you can rename the package simply by changing all the Java package statements.

Creating the TUI Toolkit

Every AWT component creates a parallel object called its "peer." In turn, peer objects implement interfaces in the java.awt.peer package. For instance, every java.awt.Button uses a peer that implements java.awt.peer.ButtonPeer. An AWT component calls peer methods to draw components on screen, and the peer interprets and forwards user input to the AWT. The peer determines the look-and-feel of a component.

The first step to building an AWT peer package is to create a class derived from java.awt.Toolkit. This class, called Toolkit, is responsible for creating the objects that implement the interfaces in java.awt.peer. Each instance of a Toolkit class represents a complete UI, including screen, keyboard, and pointer device. The UI elements that Toolkit creates are called "AWT Peers."

Listing One hows how to make AWT use a custom Toolkit. For starters, you set the awt.toolkit property to the fully qualified name of your Toolkit class. Do this before any AWT components are created to change the default Toolkit for the entire VM. To make bmsi.tui.TUIKit the default Toolkit for a specific class, I derive a class from java.awt.Frame and override the getToolkit() method to return an instance of the custom Toolkit. All java.awt Components placed in a Frame inherit that Frames Toolkit by default. (The exception to this is MenuComponent, which always uses the default Toolkit.)

Extending AWT Abstract Base Classes

Before AWT can use your Toolkit, you must implement several foundation classes:

  • java.awt.image.ColorModel, which maps pixel values used by a Toolkit to RGB.
  • java.awt.FontMetrics, which lets applications get size information about a Font and compute the size of text to be displayed in a Font supported by a Toolkit.
  • java.awt.Graphics, which lets applications draw on devices supported by a Toolkit.
  • java.awt.PrintJob, which lets applications draw on printers supported by a Toolkit.

Although ColorModel is only used for images, you still must define it. (Besides, most terminals can support icons via downloadable fonts.) TUIColorModel maps all colors to black, white, gray, or transparent -- that is, a 2-bit pixel. Some terminals do support color, but in an abstract fashion. The colors of various logical text attributes are changed to suit user preference, such as system colors in a GUI. There are no pictures where concrete colors are required.

In addition to mapping 2-bit pixels to RGB required for image processing, TUIColorModel maps colors to one of three gray levels with getTextPixel(); see Listing Two. These pixel values are used by the TUI client program to combine foreground and background colors with a font style to form a text attribute. This works well with data-oriented applications that tend to use white, gray, and black -- even in a GUI.

TUIFontMetrics describes the height and width of characters; every Font is always set to 1. You can find the full listing for TUIFontMetrics and TUIColorModel in TUIhelp.java. Listing Three shows how to make these classes available to AWT from TUIKit.

The java.awt.Graphics abstract class gives Java applications a way to draw on a Component. Unlike the other abstract classes in AWT, Graphics is returned by a Component rather than a Toolkit. Thus, the TUI version is returned by TUIComponent rather than TUIkit.

Remote Partners

I wanted to write the Java TUI package without resorting to native code, yet I needed it to be as flexible as possible. Consequently, TUIKit has little TUI code itself, but instead establishes a Socket connection to a client program that implements the UI. The two sides communicate via an RMI-like protocol called AWT Thin Client (ATC). Figure 3 shows the control flow when a user presses Return on a client Button, while Figure 4 illustrates the control flow when an application invokes setLabel() on an AWT Button.

One of the advantages of this approach is that the client program can be implemented in any language and run on any platform. My client program is currently written in C++ for terminals attached to UNIX servers. The client program can be running on the same UNIX box that is running the Java VM, but usually is not.

Also, the ATC is designed to minimize the "turnaround" that can occur when one side has to stop sending and wait for a response before continuing. This means it can work efficiently over networks. Full duplex text terminals do not work efficiently over networks because every keystroke requires a response from the server. The client program can run on a machine with a direct serial line to the terminal, while the Java program runs on a central server with a network connection. Turnaround delay is why block mode (half duplex) text terminals are often used with network connections.

Finally, the client program does not have to implement a TUI interface.

The AWT Thin Client Protocol

The ATC protocol connects the client and server with a symmetric stream of method calls. The server is an instance of TUIKit or ATCKit. The client is an independent program with a UI that is typically running on another computer. The client needs to notify the TUIKit when user input takes place, and the TUIKit needs to notify the client when AWT calls a peer method.

Each method call has two or more elements:

  • Object ID, a short integer identifying the ATC object on which to invoke the method.
  • Method ID, a short integer identifying which method to invoke. Each Method ID is given a symbolic name to use in program code. Symbolic names are defined in RemotePeer.java.
  • Arguments, zero or more integers, and strings. Number and type are determined by the Method ID. Short integers serve as Object references here as well.

Object references are symmetric. Every ATC object has a partner on the other side of the connection. When an ATC connection is first established, the Toolkit and its peer are the only ATC objects, each with an object ID of 0. The Toolkit provides methods to create and destroy ATC objects and methods for global functions such as setting screen size. Integers are transmitted in Java network format (MSB first).

Strings are transmitted as an unsigned short char (not byte) count followed by the 8-bit encoded bytes of the string. The default encoding is CP437 (IBM PC), which is supported by most ASCII terminals. An ATC client may request another encoding by invoking the SETTEXT method for the Toolkit with the Java name of the encoding. The default encoding supports 7-bit ASCII as a subset for the initial encoding change request.

TUIKit provides a set of writeCmd() methods for invoking methods on an ATC object. Listing Four shows how TUITextComponent sends a new text value to its remote partner using the SETTEXT remote method.

Immediate return values should be avoided where possible since this results in turnaround delay. When an immediate return value is required, the Toolkit (Object ID 0) provides RETSHORT and RETSTRING methods, which put the returned objects in a FIFO queue where they can be retrieved locally with getIntegerRet() and getStringRet(). The FIFO queue is only required to have one entry.

Since the Java application creates most (currently all) peer objects when it initiates, TUIKit is responsible for assigning Object IDs. This avoids the turnaround delay of obtaining IDs from the client.

All partner objects have a type, which is implied by the methodID used to create the object. This methodID can be considered the type ID of the object it creates.

ATC can be extended by assigning new peer object types and new Method ids. The current set of Method ids for ATC will fit comfortably in a byte, but I used a short integer to make sure there is plenty of room for expansion. As an example, implementing a Grid component in an ATC client might reduce network traffic and give better performance for the user.

All ATC extensions must be optional. An ATC client supporting an extension package such as GridComponents must indicate that it does so by calling the SETEXTENSION Toolkit method with a unique string identifying the extension. This method may be safely ignored if the Java Toolkit has never heard of GridComponents. This method of negotiating extensions implies that new Method ids require a central registry. On the chance that this ATC protocol becomes immensely popular and there is no central registry, negative Method IDs are reserved for local extensions.

Method IDs used to create new objects must be unique. A given object type can be defined to obey any Method ids in any manner desired. To automate ATC protocol debugging tools, however, a given Method ID should always be used with the same type and number of arguments and serve a purpose related to its symbolic name. All currently defined Method IDs are listed in RemotePeer.java and TUIGraphics.java. (TUIGraphics defines a subset of Graphics methods that make sense to a text terminal. A full ATCGraphics class is under development.) The argument types are: I (short integer), s (string), o (object reference as short integer), RI (returns an integer), and Rs (returns a string). Additionally, method.def is a C preprocessor-compatible table of ATC methods that can be used to generate C or Java code.

Back to the Toolkit

Once the ATC protocol is defined, implementing the Toolkit is straightforward. In my TUIKit implementation, all Java objects with ATC remote partners must implement the RemotePeer interface; see Listing Five.

Each RemotePeer must define remoteMethod() to execute the method IDs defined for its type of ATC object. TUIKit itself is a RemotePeer with objectID 0 and has a remoteMethod() for global commands. Executing method calls from the remote client (the program with the actual UI running somewhere else on the network) is handled by TUIKit.readCmd(); see Listing Six. It reads an Object id and Method id from the client, looks up the RemotePeer in its Object id table, and invokes remoteMethod() with the Method id for that RemotePeer. remoteMethod() is responsible for reading any additional arguments by invoking the readShort(), and readUTF() methods of TUIKit for integers and strings, respectively.

Each TUIKit instance has an event thread so that user input and screen updates can occur in parallel. Listing Seven shows that the event thread run() method calls readCmd() in a loop. Since only the event thread calls readCmd(), it does not need to be synchronized.

TUIKit runs in single- or multiuser mode. By default, TUIKit is in multiuser mode, and each instance listens for a new connection to a ServerSocket. The AWT application creating the Toolkit instance will be a login program of some description.

If TUIKit.setport(0) is called at startup, the TUIKit constructor uses FileDescriptor.out as the client connection. This is the case when the Java VM is invoked by the UNIX inetd or similar program. In this case, there is only one instance of TUIKit for a single-user VM. Finally, if the bmsi.tuikit property is set, TUIKit will use its value with Runtime.exec() to load the ATC peer on the local system and run in single-user mode.

After establishing a client connection, the TUIKit constructor reads commands from the client (by invoking readCmd()) until the client invokes SCREENSIZE. This allows the client to select options and extensions. Then an event thread is started to read commands from the client in parallel with AWT component processing. The commands from the client will consist primarily of events such as keyboard, mouse, focus, item selection, or action.

Once the ATC protocol is running, the TUIKit constructor returns, and the Toolkit is ready to create AWT peers. There are numerous AWT peer-creating methods to define, but they are all trivial; see Listing Eight.

The AWT peers we create will need to create their remote partners and invoke their methods. AWT peers call the createRemotePeer() method (see Listing Nine) to assign an Object ID. Because most components are added to a container, all containers in the remote client must implement remote methods to create any of the objects they contain. Invoking one of these methods creates the object and adds it to the container in one step. The "par" argument is the local peer of the container; the TUIKit itself is a container whose remote partner can create any object. When an AWT peer is disposed, it must remove its remote partner with removePeer().

In JDK 1.0, AWT peers called postEvent() for a target Component directly. Since each TUIKit has its own event thread, there was no interference between Toolkits with multiple instances running. JDK 1.1 introduces the EventQueue, which serializes event processing. Synchronization is simplified because there is only one thread (started by AWT) to read events from a queue and post them. AWT calls the Toolkit method to get the EventQueue; see Listing Ten.

Unfortunately, events generated internally by AWT post to the EventQueue returned by a static Toolkit method (which returns the EventQueue of the default Toolkit). This makes a multiuser Java VM vulnerable to "hanging" if a buggy or malicious application loops or waits forever while processing an AWT-generated event. This is probably acceptable when the server is running a fixed set of applications. But the Java AWT will need separate event threads for each user for a secure multiuser Java operating system. When running arbitrary applications under a multiuser operating system, each user should run their own instance of the Java VM. This uses more resources, but is secure.

For efficiency, the input and output stream for a TUIKit are buffered. This creates the problem of when to flush the output stream to avoid a deadlock. The writeCmd() method generates a delayed flush by posting an AWTEvent to the event queue; see Listing Eleven. The TUISync class is a dummy MenuItem that flushes the toolkit output buffer on an ActionEvent. The queue() method queues an ActionEvent for the dummy component. The needFlush flag avoids queueing redundant Sync events.

AWT Peers

An AWT peer is created by the addNotify() method of a Component in java.awt. The addNotify() method invokes a corresponding method in the Toolkit to create the AWT peer. The TUIComponent base class has most of the common code for the TUI AWT peer classes. A look at the TUIComponent constructor in Listing Twelve helps explain the create() and initialize() methods.

The create() method creates a remote partner of the proper type. The initialize() method copies the relevant state from the target AWT Component to the remote partner. Relevant state includes location, size, colors, and the like.

Each peer simply needs to define remoteMethod() and forward most peer calls to the remote partner. Listing Thirteen shows the complete TUIButton class, which is a simple AWT peer for TUIKit. The setLabel() method, which is unique to java.awt.peer.ButtonPeer, forwards the new label to the remote partner. TUIButton handles only one ATC Method id in remoteMethod(), MENUPICK, which indicates that the user pressed the button. The initialize() method copies the button label to the remote partner. Additional component state is initialized by super.initialize(). All AWT peers must define getMinimumSize() and getPreferredSize(). As a convenience, TUIComponent defines getPreferredSize() to call getMinimumSize() when there is no difference.

TUI peers that implement selection objects maintain the selection state locally. This keeps the selection state in sync. Listing Fourteen shows how the TUICheckbox remoteMethod() toggles the AWT Component state and posts the corresponding event when the remote checkbox is activated. The remote client does not change the state itself. It merely invokes the MENUPICK remote method and receives a SETSTATE in return.

TUIGraphics

While data-processing applications will rarely need to do any drawing beyond that provided automatically by the AWT peers, applications will sometimes want to do their own drawing on a Canvas or Panel. One of the drawing methods in java.awt.Graphics is drawString(), which has an obvious implementation. DrawRect() is also useful where line-drawing glyphs are available, as is drawLine (and hence, polyLine) when the lines are horizontal and vertical. copyArea is also straightforward. TUIGraphics forwards any drawing commands that are remotely feasible in text mode to the remote component.

The most important job of java.awt.Graphics is to keep track of graphics state information for each instance. This avoids having to specify every parameter for every drawing command. Listing Fifteen shows the state variables for TUIGraphics.

TUIGraphics does not have a remote partner. Instead, the drawing commands are sent to the remote partner of its associated TUIComponent target. This arrangement lets the remote client manage its own graphics contexts, which is especially important when the remote client is a GUI. Since multiple Graphics instances can be created for a Component, TUIGraphics checks in the getID() method whether this instance was the last instance to send Graphics commands through the Component. If not, the current Graphics state is retransmitted before invoking the drawing command. Each remote partner must maintain a Graphics state independently of any state used for local painting. As Listing Sixteen shows, the getID() method transmits the graphics state when required and returns the TUIComponent target where the drawing will occur.

The setXXX methods need to transmit the state information immediately when the TUIGraphics instance currently owns the component; see setClip() in Listing Seventeen.

For this scheme to work with multiple threads drawing on a single component, most methods need to be synchronized on the target TUIComponent. I didn't do this because it is messy and I couldn't think of a situation where I would want multiple threads drawing on a single component. Multithreading works fine as long as there is only one thread per component.

Conclusion

Apart from Image support, a GUI ATC client needs only a few extensions to the protocol. ATCColorModel maps RGB to 16-bit pixels. ATCTextMetrics needs to actually query the remote client. Since this is slow, ATCKit needs to cache the size of average text samples by Font for use in getMinimumSize() and friends.

Despite a few minor flaws, the Java AWT lives up to its name with amazing adaptability. I hope that Java AWT Client (or something like it) will help make server- based Java applications more practical.


Listing One

class SomeClass {  static void main(String args[]) {
 Properties props = System.getProperties();
 props.put("awt.toolkit","bmsi.tui.TUIKit");
  }
import java.awt.*;
class TUIFrame extends Frame {
  public Toolkit getToolkit() {
 return new bmsi.tui.Toolkit();
  }
}

Back to Article

Listing Two

class TUIColorModel extends java.awt.image {  static int getGrayLevel(Color c) {
 int red = c.getRed();
 int blue = c.getBlue();
 int green = c.getGreen();
 return (red + blue + blue + green) / 4;
  }
  static int getTextPixel(Color c) {
 int gray = getGrayLevel(c);
 if (gray < 32) return 0;
 if (gray < 192) return 1;
 return 2;
  }
}

Back to Article

Listing Three

class TUIKit extends java.awt.Toolkit {  private TUIColorModel colorModel = new TUIColorModel();
  private String[] fontlist = { "Terminal" };


</p>
  public ColorModel getColorModel() { return colorModel; }
  public String[] getFontList() { return fontlist; }
  public FontMetrics getFontMetrics(Font f) {
 return new TUIFontMetrics(f);
  }
}

Back to Article

Listing Four

class TUITextComponent extends TUIComponent  implements TextComponentPeer {
  private String txt;
  void setText(String s) {
 txt = s;   // keep a local copy
 toolkit.writeCmd(this,SETTEXT,s);
  }
}

Back to Article

Listing Five

interface RemotePeer {  /** execute a command from our remote partner */
  void remoteMethod(int cmd) throws IOException;
  /** return the object id our remote partner knows us by. */
  int getID();
  /** return the java.awt Component or MenuComponent for this
   peer. */
  Object getTarget();
}

Back to Article

Listing Six

class TUIKit extends Toolkit implements RemotePeer {  private Vector objs;
  private DataInput in;
  /** wait for an input event from our remote client. */
  private void readCmd() {
 try {
   flush();
   int id = in.readShort();
   int cmd = in.readShort();
   RemotePeer peer = (RemotePeer)objs.elementAt(id);
   peer.remoteMethod(cmd);
 }
 catch (IOException e) {
   throw new AWTError(e.toString());
  }
 }
}

Back to Article

Listing Seven

class TUIKit extends Toolkit implements Runnable, RemotePeer {  public void run() {
 try {
   for (;;)
     readCmd();
 }
 // remote client has gone away
 catch (AWTError e) {
   debug(e);
   // exit if loaded via inetd
   if (server == null)
     System.exit(0);
  }
 }
}

Back to Article

Listing Eight

class TUIKit extends Toolkit implements RemotePeer {  protected TextFieldPeer createTextField(TextField t) {
 return new TUITextField(t,this);
  }
}

Back to Article

Listing Nine

class TUIKit extends Toolkit implements RemotePeer {  synchronized int createRemotePeer(RemotePeer par,
  int type,RemotePeer peer) {
 if (par == null)
   par = this;
 /* Vector won't search for null, so we search for ourselves
 knowing that a real TUIKit reference will be in pos 0 only */
 int id = objs.indexOf(this,1);
 if (id < 0) {
   id = objs.size();
   objs.addElement(peer);
 }
 else
   objs.setElementAt(peer,id);
 writeCmd(par,type,id);
 return id;
  }
  void removePeer(int id) {
 if (id < 0) return;
 objs.setElementAt(this,id);
 writeCmd(id,DISPOSE);
  }
}

Back to Article

Listing Ten

class TUIKit {  EventQueue theQueue = new EventQueue;
  protected EventQueue getSystemEventQueueImpl() {
 return theQueue;
  }
}

Back to Article

Listing Eleven

class TUIKit extends Toolkit implements RemotePeer {  flushTarget = new TUISync(this);
  synchronized void writeCmd(RemotePeer peer,int cmd) {
 writeCmd(peer.getID(),cmd);
 if (!needFlush) {
   needFlush = true;
   flushTarget.queue();
  }
 }
}

Back to Article

Listing Twelve

class TUIComponent implements ComponentPeer, RemotePeer {  protected Component target;
  protected TUIKit toolkit;
  protected int winID = -1;
  protected TUIComponent(Component comp,TUIKit toolkit) {
 this.toolkit = toolkit;
 target = comp;
 Container parent = target.getParent();
 TUIContainer parentpeer = null;
 if (parent != null)
   parentpeer = (TUIContainer)parent.getPeer();
 create(parentpeer);   // create our remote partner, set winID
 initialize();         // copy target state to remote partner
  }
}

Back to Article

Listing Thirteen

class TUIButton extends TUIComponent implements ButtonPeer {  TUIButton(Button but,TUIKit toolkit) { super(but,toolkit); }
  protected void create(TUIContainer parent) {
 winID = toolkit.createRemotePeer(parent,NEWBUTTON,this);
  }
  public Dimension getMinimumSize() {
 Button but = (Button)target;
 return new Dimension(but.getLabel().length(),1);
  }
  protected void initialize() {
 Button but = (Button)target;
 String text = but.getLabel();
 if (text != null)
   setLabel(text);
 super.initialize();
  }
  public void setLabel(String s) {
 toolkit.writeCmd(winID,SETTEXT,s);
  }
  public void remoteMethod(int cmd) throws IOException {
 if (cmd == MENUPICK) {
   Button but = (Button)target;
   toolkit.theQueue.postEvent(
     new ActionEvent(but,
       ActionEvent.ACTION_PERFORMED,but.getActionCommand()
     )
   );
   return;
 }
 super.remoteMethod(cmd);
}

Back to Article

Listing Fourteen

class TUICheckbox extends TUIComponent implements CheckboxPeer {  public void remoteMethod(int cmd) throws IOException {
 if (cmd == MENUPICK) {
   Checkbox cb = (Checkbox)target;
   boolean flag = !cb.getState();
   cb.setState(flag);
   toolkit.theQueue.postEvent(
     new ItemEvent(cb,
       ItemEvent.ITEM_STATE_CHANGED,cb.getLabel(),
       flag ? ItemEvent.SELECTED : ItemEvent.DESELECTED
     )
   );
   return;
 }
 super.remoteMethod(cmd);
 }
}

Back to Article

Listing Fifteen

class TUIGraphics extends Graphics {  private TUIKit toolkit;
  private TUIComponent target;
  private Color color;
  private Font font;
  private Rectangle clipRect;
  private int posx, posy;
  private boolean XORmode;
}

Back to Article

Listing Sixteen

class TUIGraphics extends Graphics {  private RemotePeer getID() {
 if (target.currentGraphics != this) {
   toolkit.writeCmd(target,INITGRAPHICS);
   target.currentGraphics = this;
   if (font != null)
     setFont(font);
   // Should XORmode affect color?
   if (color != null)
     setColor(color);
   if (clipRect != null)
     setClip(clipRect);
   if (posx != 0 || posy != 0)
     translate(0,0);
  }
 return target;
 }
}

Back to Article

Listing Seventeen

class TUIGraphics extends Graphics {  public void setClip(Shape s) {
 clipRect = s.getBounds();
 if (target.currentGraphics == this)
   toolkit.writeCmd(target,CLIPRECT,
     clipRect.x,clipRect.y,clipRect.width,clipRect.height);
  }
}

Back to Article

DDJ


Copyright © 1997, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.