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.
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.
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.
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.
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");
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:
|
-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. |
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:
Attributes.getInstance().get(MyClass.class).get("mytag");
Attributes.getInstance().get(MyClass.class).has("mytag");
Attributes.getInstance() .get(MyClass.class.getField("myfield")) .get(TxAttribute.class);
Attributes.getInstance() .get(MyClass.class.getMethod("toString", null)) .iterator();
Attributes.getInstance() .get(MyClass.class) .iterator("my_multiple_tag");
Many other accessors are available; check the Javadocs for details. A few things to keep in mind:
toArray()
will
respect it. Hence, the ordering of tags can be meaningful if you'd like.
SimpleAttribute
.)
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