QDox Attributes Usage Manual

Declaring attributes in source code

Compiler invocation

Runtime access to attributes

Compacting attribute files in a JAR


Declaring attributes in source code

To declare an attribute in your code, add a tag to the Javadoc comment just before the element in question. For example:

/**
 * Central access point to all attribute-related methods.
 * 
 * @pattern singleton
 */
public class Attributes {
   // ...
}

You can add these attributes to any element that can normally have Javadoc comments, including classes, interfaces, fields and methods. You can also continue to use the standard Javadoc tags (e.g. @author, @param, etc.), and they will by default be ignored by the attributes module. (See the compiler section below for details.)

The syntax of each attribute tag can generally be anything you want, though constraints can be imposed depending on the attribute compiler mode. By default, the compiler runs in "string" mode, where each tag is converted into a string key/value pair. The tag's name up to the first whitespace character (pattern above) is taken as the key, and the remainder of the line (after the whitespace) as its value (singleton above). The tag's name is not verified in any way, and the value is not parsed. This is the simplest way to use attributes.

Object attributes

To gain more control over attribute validation, run the compiler in "object" mode. The attribute compiler then assumes that each tag refers to an attribute class that it will attempt to find an instantiate. The tag's name up to the first whitespace is taken as the name of the class. The name can be fully qualified, or it can rely on the compilation unit's declared package or imports to find the correct class, taking advantage of the usual Java type resolution rules. The name can also refer to a nested class in the usual way, by separating nested class names with dots (e.g. Server.TransactionAttribute). If the class name ends with Attribute, this suffix can be omitted from the tag but an error will be reported if the resulting tag name is ambiguous (i.e. it is a valid class name both with and without the suffix).

(You can also run the compiler in "mixed" mode, where any tag that is not recognized as an attribute class name will be processed as a simple "string" attribute. This mode is best used while transitioning from unstructured to structured attributes.)

Attribute classes should normally be serializable, so that attribute instances can be saved directly in the compiled attribute files. If an attribute is not serializable, however, it will be stored as its class name and string parameters, and recreated at runtime as necessary. This imposes a runtime performance penalty, but may allow for decreased memory usage in the future when attribute interning is implemented.

Typed parameters

Once the attribute class has been found, the compiler will attempt to instantiate it by using the tag's parameters. Two kinds of parameters are recognized: positional and named. All positional parameters must precede all named parameters, though both sets are optional. Parameters are separated by whitespace. The positional parameters will be used to locate a constructor for the attribute class that takes the same number of parameters as there are positional parameters.

The named parameters will be matched to properties of the attribute class, and each property's set method will be invoked with the given string as argument. Properties will be located using standard JavaBeans introspection. The order of named parameters is usually not important, though this depends on whether the properties mentioned are all independent of each other. In any case, the properties will be set in the order listed in the tag. Either the constructor or any of the property setters can throw exceptions (checked or unchecked) to indicate that the parameters are unacceptable; these will be reported as tag syntax errors by the compiler.

Typically, positional parameters are use for compulsory parameters, and named parameters for optional ones.

Both constructors and property setters are found based on the number of parameters only. If you have multiple constructors with the same number of parameters overloaded on parameter type, or similarly overloaded setters, any attempt to use them for attribute construction will be flagged as an error. This deviates from standard Java overloading practices, but allows the attibute compiler to be more flexible with the values' syntax.

Parameter values

To provide the value for a parameter (whether positional or named), you have two options:  you can either refer to an existing constant, or provide a literal value.  To refer to a constant (a public static final field), provide its name in the usual Java syntax. If the name is at least partially qualified, it will be resolved according to the current scoping rules; remember that an attribute applied to a class is outside the scope of that class. If the constant reference is unqualified, normal scoping does not apply. Instead, the field will be looked up in the attribute's class and in the current parameter's class. (If it shows up in both, it is ambiguous and an error will be reported.) This departs from normal Java resolution practices, but allows you to easily specify values of a Typed Enumeration pattern, or special constants defined in the attribute class.

If the value does not resolve to a field reference, it is interpreted as a literal value instead. Literals are only valid for parameters of primitive type (or their corresponding wrapper types), String and Class. The literal is parsed using fairly unsurprising rules, though only the simple decimal form is supported for numbers (i.e. no 0xff allowed). For Class type parameters, the value is interpreted as the name of a type and resolved according to the current scope, respecting all import statements. You must not append .class to the type name.

If you wish to include whitespace or other special characters in a String parameter, or you wish to specify a literal parameter that happens to match a field name, simply enclose the parameter in double quotes. Inside the double quotes, spaces will be considered as part of the parameter. To include a double quote in the string, use \", use \\ for a backslash, and \n and \t to insert a newline or tab respectively.

To specify literal array-typed values, surround the array with braces. You must normally have a space in front of the opening brace and after the closing brace, but these rules are relaxed when nesting arrays. Inside an array, you can only have positional arguments that must all be of the array's type. Multi-dimensional arrays must be homogeneous, though they can be ragged.

Object attribute example

If you declared the following attribute class:

public class MetaAttribute {
  public MetaAttribute(Target[] targets) {...}
  public void setAllowMultiple(boolean allowMultiple) {...}
  public void setDisplayName(String displayName) {...}
  
  public static class Target {
    public static final Target CLASS = new Target("class");
    public static final Target METHOD = new Target("method");
    public static final Target FIELD = new Target("field");
    private Target(String name) {...}
  }
}

A sample use might be as follows:

@Meta {FIELD METHOD} allowMultiple=false displayName="My favourite attribute"

This tag would be instantiated as follows:

MetaAttribute attr = new MetaAttribute(new Target[]{
  MetaAttribute.Target.FIELD, MetaAttribute.Target.METHOD
});
attr.setAllowMultiple(false);
attr.setDisplayName("My favourite attribute");

Compiler invocation

This section concentrates on the command-line compiler. The Ant task works similarly, but all parameters are passed through the build definition file instead. Make sure you have attrib-dev.jar and the included qdox-tiny.jar on your classpath. Also present on your classpath should be all the classes normally needed to compile the code you'll be compiling attributes for, and the attribute classes themselves, if any.

To compile all attributes in your project, change to the root of your source hierarchy (i.e. the package root) and run the compiler:

java com.thoughtworks.qdox.attributes.compiler.Compiler

The resulting attribute files will be placed next to the source files. This works best if you normally generate your class files in the same place as your source files. If you use separate directories, say "source" for source files and "build" for build files, use this command line from your project root directory instead:

java com.thoughtworks.qdox.attributes.compiler.Compiler -src source -dst build

This will compile the attributes in all source files in the directories below "source" and place the attribute files into matching subdirectories of "build". If you have multiple source and build directories, you can list them all (type all of this on one line):

java com.thoughtworks.qdox.attributes.compiler.Compiler
   -src source;tests -dst build;testbuild

All source files in the "source" and "tests" directories will be compiled, and the attribute files will be placed wherever the matching class files are found in "build" and "testbuild". (If a source file doesn't have a matching class file, the attribute file will be placed into the first destination directory listed, "build" in this case.) The source and destination directories are separated by the same character used to separate classpath entries (normally ";" on Windows and ":" on Unix).

Finally, if you only want to compile attributes for classes in the com.example.foo package and subpackages, even though its source files are spread across the source and tests directories, do this:

java com.thoughtworks.qdox.attributes.compiler.Compiler
   -src source;tests -dst build;testbuild com/example/foo

You can list any number of directories (and Java source code files) in this manner, and only those files and directory hierarchies will be considered by the attribute compiler.

A full list of compiler options follows:

-help Print usage and exit.
-src <source paths> Specify the list of package root directories that contain source code. Separate directories with the platform's path separator character (like classpath). If not specified, use the current directory as the default source root.
-dst <destination paths> Specify the list of package root directories that will receive compiled attribute files. Separate directories with the platform's path separator character (like classpath). Each attribute file will go into the same directory that contains the matching class file, or the first directory listed if none is found. If not specified, use the source paths as the default destination paths.
-mode <string|object|mixed> Set the compiler parsing mode:
  • "string" considers all tags as simple string key/value attribute pairs
  • "object" considers each tag as referring to an attribute class that it attempts to instantiate using the given parameters; attribute classes must be on the current classpath and the compiler reports an error if a matching class is not found
  • "mixed" tries to find a class for each tag like "object", but if no class can be located processes the tag as "string" without reporting an error
The default mode is "mixed".
-ignore <tags to ignore> Set the list of tags to ignore when processing. The tags must not include the @ sign, and must be comma-separated. If not specified, default to ignoring all standard Javadoc tags, so that they won't be processed as attributes. If specified, though, standard Javadoc tags are not automatically ignored and must be listed if so desired.
-force Force all attribute files to be regenerated, regardless of file timestamps.
-nocleanup Do not remove attribute files with no matching source file. You must use this option if you have non-public classes in files whose names don't match the class name.
-verbose Print extra information on the compilation process.

Runtime access to attributes

At runtime, you need to have the attrib-rt.jar (or attrib-dev.jar, which is a superset) on your classpath. To access attributes, use the com.thoughtworks.qdox.attributes.Attributes singleton. Some typical uses:

Many other accessors are available; check the Javadocs for details. A few things to keep in mind:

Compacting attributes in a JAR

When putting your application in a JAR, make sure that all the attribute files generated by the compiler stay with their matching class files. That's it -- you don't need to do anything more!

As an optional step, you can compact the attribute files inside the JAR into one big attribute file. This should decrease the overall loading time of attributes, and may reduce the overall size as well. To compact the attribute files, run the following command line passing it any number of JAR filenames as arguments. They will be compacted in place.

java com.thoughtworks.qdox.attributes.dev.JarCompacter myfilename.jar