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.

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).  Furthermore, if the name does not end with Attribute, this word will be appended before looking for a class; however, if this fails, the original name will be tried too.

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.

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 String 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.  For example:

@Tx write pessimistic isolate=full

This attribute will be instantiated as follows:

TxAttribute attr = new TxAttribute("write", "pessimistic");
attr.setIsolate("full");

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.

If you wish to include whitespace or other special characters in your parameters, simply enclose them in double quotes.  Inside the double quotes, spaces will be considered as part of the parameter.  (This also works on named parameter values.)  To include a double quote in the string, use \", use \\ for a backslash, and \n and \t to insert a newline or tab respectively.

Finally, 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.

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.ideanest.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.ideanest.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.ideanest.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.ideanest.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:
bullet"string" considers all tags as simple string key/value attribute pairs
bullet"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
bullet"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.ideanest.attributes.Attributes singleton.  Some typical uses:

Get the value of a simple string attribute for a class:
Attributes.getInstance().get(MyClass.class).get("mytag");

To check whether a simple string attribute with a given tag was defined for a class:
Attributes.getInstance().get(MyClass.class).has("mytag");

Get the value of an object attribute for a field:
Attributes.getInstance()
  .get(MyClass.class.getField("myfield"))
  .get(TxAttribute.class);

Get an iterator for all the attributes of a method:
Attributes.getInstance()
  .get(MyClass.class.getMethod("toString", null))
  .iterator();

Get an iterator for all values of string attributes with a given tag for a class:
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:

bulletThe order of all attributes is preserved for each element, and all iterators and toArray() will respect it. Hence, the ordering of tags can be meaningful if you'd like.
bulletString and object attributes never overlap.  Each tag is parsed as either one or the other, never both.  Thus, you will not find object attributes by querying for the tags used to define them.  (However, you will find string attributes as object attributes of class SimpleAttribute.)
bulletYou can access attribute bundles using coded strings rather than reflected element references.  This can be useful if the element you wish to get the attributes for is not visible from the accessing code, or you need to access the attributes before the class containing the element is loaded.
bulletYou can add (and sometimes remove) attributes at runtime, but these changes will not persisted back to disk.

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.ideanest.attributes.dev.JarCompacter myfilename.jar