Logging I18n Tooling Technical Requirements

Abstract

JBoss Logging 3 is a new and improved logging facade library which includes support for internationalized logging by way of an annotated interface mechanism.  These interfaces are designed to be used as input into build tools which generate classes that implement them, using various string translations.  This document describes the technical requirements for the build tools.

 

Constraints

The following annotations are defined:

 

  • @org.jboss.logging.MessageBundle - This annotation is attached to an interface which is intended to be a message bundle (a generic source of translated strings; i.e. there are no logging methods on the interface).  Interfaces annotated with this annotation can be instantiated via the Messages.getBundle(InterfaceName.class) method.
  • @org.jboss.logging.MessageLogger - This annotation is attached to an interface which is intended to act as a translating message logger.  Such interfaces may include plain bundle messages as well as logger messages.  Interfaces annotated with this annotation can be instantiated via the Logger.getMessageLogger(InterfaceName.class, "category.name") method.
  • @org.jboss.logging.Message - this annotation is attached to any interface method which corresponds to a message which may be translated.  This includes both simple messages and log messages.
  • @org.jboss.logging.LogMessage - this annotation is attached to log messages (in addition to Message above), and includes additional information such as the log level for the message.
  • @org.jboss.logging.Cause - this annotation is attached to a method parameter which should be considered to be the causing java.lang.Throwable (or subclass thereof).

 

Message bundle interfaces must conform to these rules:

  • The message bundle annotation may specify a project code.
  • All methods defined on the message bundle interface must have a @org.jboss.logging.Message annotation present, unless the method has the same name as another method on the interface which has the annotation present; otherwise, a compiler error should result.
  • All methods defined on the message bundle interface must return either java.lang.String or one of its supertypes, or java.lang.Throwable or one of its subtypes; otherwise, it should result in a compile error.
  • All methods defined on the message bundle interface must accept a number of parameters consistent with the format string on the value attribute of the @org.jboss.logging.Message annotation.  This assertion is complex to ascertain at compile-time, thus the compile time tool need not validate this directly.
  • The @org.jboss.logging.Cause annotation may appear at most once on any given method's parameter list.
  • Multiple methods with the same name are permitted, as long as they meet all of the following criteria:
    • They have the same number of non-@Cause parameters.
    • Only one of the methods may specify a @org.jboss.logging.Message annotation.
  • If the method returns a java.lang.Throwable, the parameter marked as @Cause is passed in to that Throwable's constructor if it has such a parameter; if not, then it is passed in to the Throwable's initCause() method after the Throwable is constructed.
  • All of the @org.jboss.logging.Message annotations found on methods on all message bundle interfaces with the same project code which specify an id must specify a unique number, INHERIT, or NONE.  Failure to do so should result in a compile error.
  • A message bundle interface may extend other message bundle interfaces.
  • A message bundle interface may extend java.io.Serializable; however, doing so is superfluous as the implementation class will implement this interface regardless.
  • A message bundle interface may not extend any other interfaces which do not fit the criteria specified above.

 

Message logger interfaces must conform to the above rules, except:

  • A message logger interface may not specify a @org.jboss.logging.MessageBundle annotation; instead, it must specify a @org.jboss.logging.MessageLogger annotation (which also has a property for project code).
  • Any method on a message logger interface may additionally specify a @org.jboss.logging.LogMessage annotation.  This annotation signifies that the method is a logger method.
  • All logger methods must be declared to return void.
  • A logger method may have a specified log level in the level property of the @LogMessage annotation.
  • If multiple methods of the same name exist, any number of them may have @LogMessage annotations.  Only methods so annotated are log message methods.  The rules regarding equally-named methods on message bundles continue to apply.
  • Log methods with a parameter marked as @Cause will pass that parameter in to the appropriate log method as the causing exception.
  • Message logger interfaces may extend other message logger interfaces in addition to the rules for message bundle interfaces.

 

Tool Requirements

Annotation Processor

The first required tool is the annotation processor.  This tool reads and analyzes the annotation types above to produce the core bundle classes.  The specification for this tool is as follows:

  • A single class (known as the "primary class") is generated for every message bundle and message logger interface.  This class:
    • must be public
    • must not be final or abstract
    • must extend java.lang.Object
    • must implement java.io.Serializable
    • should be annotated with the @javax.annotation.Generated annotation
    • should contain a warning initial comment that the class consists of generated code
    • must contain a spec-complaint serialVersionUID field initialized to the value 1
    • must implement all message and logger interface methods (as "public final") on the corresponding interface and its supertypes.  Each method must:
      • yield the formatted result of applying the appropriate format strategy to the format string plus its arguments
      • if the @Message annotation has a specified id, and a project code on the interface whose primary class is being generated (possibly a supertype of the message's interface), prepend the project code, followed by a hyphen ("-"), followed by the message ID (zero-padded to five digits)
    • must be named: org.pkgname.InterfaceName$bundle if it is a message bundle implementation, or org.pkgname.InterfaceName$logger if it is a message logger implementation
    • must contain a private static constant string field for every value specified on every @org.jboss.logging.Message annotation on all methods of the interface and its supertypes
    • must define a protected instance method called interfaceMethodName$str for every distinct interface method name defined on the interface and its supertypes.  This method:
      • must return java.lang.String
      • must accept no parameters
      • must be declared protected
      • must not be declared abstract or final
      • must perform no function other than to return the constant string field value corresponding to the @org.jboss.logging.Message annotation on the named interface method
  • In addition, if the class is a message bundle class:
    • There must be a protected, no-arg constructor which performs no actions.
    • There must be a public static constant field named INSTANCE which holds an instance of the class
    • There must be a (non-final) protected method called readResolve declared which takes no parameters, throws no exceptions and returns the value of the INSTANCE field
  • In addition, if the class is a message logger class:
    • There must be a protected final (non-static) field named log of type org.jboss.logging.Logger.
    • There must be a single public constructor which accepts an instance of org.jboss.logging.Logger and assigns it to the log field
    • All log method implementations in the class use this logger instance to log the corresponding message string and parameters.

 

Translation Tool

The second required tool is the translation tool.  The purpose of this tool is to take translations defined in properties files and convert them into classes which implement a set of strings which apply to a specific locale.  The requirements are as follows:

  • The tool must accept the following inputs:
    • The original module source code
    • A list of property files following the name convention: "org.pkgname.InterfaceName.i18n_locale.properties" where locale is a standard locale name with "_" separators.
  • Any property file encountered with a name which does not correspond to a message bundle or message logger interface should be ignored for the purposes of processing; in addition, a warning should be issued stating that fact.
  • The locale part of the file name is required to be of one of the following forms (each "_"-separated piece is called a "section"):
    • xx - where xx is the language (e.g. "en")
    • xx_YY - where xx is the language and YY is the country (e.g. "en_US")
    • xx_YY_ZZ - where xx is the language, YY is the country, and ZZ is the variant ("en_US_POSIX")
  • For each file, a translation class is generated.  This class has the following characteristics:
    • It is a subtype of the corresponding primary class for that interface.
    • If the locale name has only one section, it directly extends the primary class.
    • If the locale name has multiple sections, it extends the translation class of the enclosing locale; that is, the locale whose name is equal to the locale for this class with its last section trimmed.  If such a class does not exist, and is not specified by another property file, then an otherwise empty one should be created.
    • An overriding protected interfaceMethodName$str method exists for every entry in the file that specifies a translation for a message or logger interface method.  Such an entry's property name will be identical to the corresponding interface method name.  If an entry exists in the file which does not correspond to an existing method name, it should be ignored and a warning should be issued.  If an entry exists with a matching name, but the value is empty or only consists of whitespace, that entry should be ignored.
    • If the translation class corresponds to a message bundle interface, it has the following additional characteristics:
      • A protected, no-arg constructor which simply invokes the superclass constructor
      • A public static final field named INSTANCE which is initialized to an instance of the translation class
      • An overriding non-final protected method called readResolve which takes no parameters, throws no exceptions, and returns the value of the INSTANCE field
    • If the translation class corresponds to a message logger interface, it should have a matching protected one-arg constructor accepting a org.jboss.logging.Logger which delegates to the superclass constructor.
  • The tool may optionally provide a mechanism to generate "skeletal" translation files which do not have values filled in, and which present the original message text above each entry as a comment.  This will make it easier for a translator to provide message translations.