Laf-Widget : How It Works

# How it works

## Delegate augmentation

If you need to debug a look-and-feel that has been augmented automatically, you need to be aware of the naming convention of the augmented code. The delegate augmentation takes an existing look-and-feel UI delegate class and changes it in the following way:

- If the class has lafWidgets member (no matter of which type), the augmentation is cancelled and the class remains intact.
- A new member is added to the class. The member definition is protected Set lafWidgets. This set will be populated at runtime (in the installUI method) with the relevant widgets.
- Each overriding install / uninstall method (at most eight) is renamed (the logic inside the original method is preserved). The naming convention prepends the fully-qualified class name to the method name (this is to prevent name collisions for inner-LAF extending UI delegates - see example below). The slashes in the class name are replaced by double underscores. The prefix starts with double underscores as well - see example below.
- For each non-overriding install / uninstall method (defined in one of the base LAF classes but not overriden in the specific UI delegate), a new forwarding method is created. The method name follows the convention above. The method implementation contains a single call to the super implementation.
- For every install / uninstall method (overriding or not), a new implementation is created. The implementation calls the previously created method (either renamed original implementation or newly created forwarding implementation). Afterwards, the implementation calls the relevant widget lifecycle function.

Here is an example that illustrates the above. Suppose we have a custom UI delegate for tabbed panes which looks like (relevant install / uninstall methods only):
`public class CustomTabbedPaneUI extends MetalTabbedPaneUI {
public void installUI(JComponent c) {
super.installUI(c);
// do something
}
protected void installListeners() {
super.installListeners();
// do something
}
}`
After augmenting this UI delegate, the bytecode will contain the following implementation (not that the complete implementation will contain all install / uninstall methods):
`public class CustomTabbedPaneUI extends MetalTabbedPaneUI {
protected Set lafWidgets;
// This method is renamed (preserving original implementation)
public void __org__jvnet__lafwidget__ant__temp__installUI(JComponent c) {
super.installUI(c);
// do something
}
// This method is renamed (preserving original implementation)
protected void __org__jvnet__lafwidget__ant__temp__installListeners() {
super.installListeners();
// do something
}
// This method is created automatically (forwarding implementation)
protected void __org__jvnet__lafwidget__ant__temp__installComponents() {
super.installComponents();
}
public void installUI(JComponent c) {
this.lafWidgets = LafWidgetRepository.getRepository()
.getMatchingWidgets(c);
this.__org__jvnet__lafwidget__ant__temp__installUI(c);
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installUI();
}
}
protected void installListeners() {
this.__org__jvnet__lafwidget__ant__temp__installListeners();
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installListeners();
}
}
protected void installComponents() {
this.__org__jvnet__lafwidget__ant__temp__installComponents();
for (Iterator it = this.lafWidgets.iterator(); it.hasNext();) {
LafWidget lw = (LafWidget) it.next();
lw.installComponents();
}
}
}`
As can be seen, the following changes have been made:

- lafWidgets field added.
- Existing methods renamed with the logic preserved (see __org__jvnet__lafwidget__ant__temp__installUI and __org__jvnet__lafwidget__ant__temp__installListeners).
- Non-overriden methods - created forwarding delegates (see __org__jvnet__lafwidget__ant__temp__installComponents method).
- Implementation of install / uninstall methods calls either original implementation (see installUI and installListeners) or the generated forwarding implementation (see installListeners). In addition, corresponding lifecycle methods of registered widgets are invoked.
- Implementation of installUI queries the LafWidgetRepository for all available widgets that match the component and initializes the lafWidgets set.

Here is the original stack trace of sample application under Metal look-and-feel:
As can be seen, the call to installUI executes code in BasicButtonUI (since MetalCheckBoxUI doesn't override this method). This function calls the installDefaults (which is overriden in MetalCheckBoxUI) that gets to MetalCheckBoxUI which calls the super implementation in MetalRadioButtonUI and so on. The matching stack trace in the augmented code (Substance):
In the augmented code, we have forwarding implementation of installUI in the SubstanceCheckBoxUI which is called after we get the associated widgets. The execution eventually gets to the original implementation in the BasicButtonUI and goes from there back to installDefaults of SubstanceCheckBoxUI which calls the generated forwarding implementation as well. Eventually, we get to the original method in MetalRadioButtonUI.
This stack trace illustrates the need for the prefix of generated methods. Without this perfix we would have stack overflow since SubstanceCheckBoxUI extends SubstanceRadioButtonUI.

## LAF augmentation

The LAF augmentation follows the same principle. The main LAF class undergoes the following changes:

- The original initClassDefaults method is renamed.
- New initClassDefaults method is created. The implementation calls the original (renamed) method and then puts additional pairs into the UIDefaults table. Each pair contains a "missing" (unimplemented) component UI ID and the class name of a generated UI delegate.

In addition, this process generates component UI delegates that contain implementations of createUI and forwarding constructor (w/o arguments or with single argument according to the core UI delegate). These delegates should then undergo the delegate augmentation as described above. The naming convention for the generated delegates is __Forwarding__ plus the component UI ID. The generated delegates are put under the same package as the main look-and-feel class:

## Finding widgets at runtime

The LafWidgetRepository employs flexible mechanism for the runtime lookup and instantiation of widgets. The information on available widgets is searched in META-INF/lafwidget.properties resources in the classpath. This means that you can put additional (third-party or your own) widgets in the classpath (as jars or simple classes) and they will be picked at runtime.
The base edition of this project comes with the following lafwidget.properties:
`org.jvnet.lafwidget.combo.ComboboxAutoCompletionWidget = javax.swing.JComboBox
org.jvnet.lafwidget.desktop.DesktopIconHoverPreviewWidget = javax.swing.JInternalFrame$JDesktopIcon
org.jvnet.lafwidget.menu.MenuSearchWidget = javax.swing.JMenuBar
org.jvnet.lafwidget.tabbed.TabHoverPreviewWidget = javax.swing.JTabbedPane
org.jvnet.lafwidget.tabbed.TabOverviewDialogWidget = javax.swing.JTabbedPane
org.jvnet.lafwidget.tabbed.TabPagerWidget = javax.swing.JTabbedPane
org.jvnet.lafwidget.text.PasswordStrengthCheckerWidget = javax.swing.JPasswordField
org.jvnet.lafwidget.text.LockBorderWidget = javax.swing.text.JTextComponent;javax.swing.JComboBox
org.jvnet.lafwidget.text.SelectAllOnFocusGainWidget = javax.swing.text.JTextComponent
org.jvnet.lafwidget.text.EditContextMenuWidget = javax.swing.text.JTextComponent
org.jvnet.lafwidget.tree.dnd.TreeDragAndDropWidget = javax.swing.JTree
`
The format of this file is very simple - each line contains the key and the value. The key should be the fully qualified class name of a widget. If no such class is found, the class doesn't implement LafWidget interface or doesn't have default constructor, the matching entry will be ignored. The value is semicolon-separated list of fully-qualified class names of components that can be "decorated" with the relevant widget.
At runtime, the implementation of LafWidgetRepository.getMatchingWidgets searches the entire hierarchy of the specified component and instantiates all widgets that match either the component class or one of its super classes. In addition, it doesn't instantiate widgets that return `false` from requiresCustomLafSupport when there is not custom LAF support set.
This implementation allows safely removing any (or all) widgets from the final archive of a specific look-and-feel that uses this library. For example, if you (as a look-and-feel developer) decide to not use the tabbed pane widgets, you can safely remove all classes from org.jvnet.lafwidget.tabbed package (without changing the lafwidget.properties). This means that you can effectively use the widgets as building blocks to build your own "feel" part of the application.