GWT

Introduction

StrataCode lets you build powerful frameworks by providing a read-modify-write parser, a powerful dependency and build system, and by exposing just the right set of hinge points to let you assemble and customize the whole thing. A layer definition file may add normal Java jar files. When that layer is activated, a new class loader is created adding those classes. The layer definition file can specify imports that are available to downstream layers.

Code generation features provide flexible mapping of how an identifier expression like "a.b" is mapped to get and set methods. The dynamic runtime lets you implement dynamic abstractions which can later be used more efficiently in a compiled mode.

StrataCode provides automatic data binding event injection. You can flexibly designate classes that need component initialization semantics (using the @Component annotation).

The layer definition file lets you register handlers for annotations - which affect how the code is generated when those annotations are present. You can associate compile-time code templates with classes for operations like object creation, the new operator, getX/setX conversion handling. Support dynamic updates of code in development which is later compiled to production.

A StrataCode framework mainly consists of one or more layers, wrapper classes which inject data binding events, annotation layers which can set compile-time annotations on any class. These annotations typically control how those classes are used - including, adding the @Component attribute, marking properties as bindable, constant, or adding code templates for how "object" tags or scopes are interpreted (see @CompilerSettings, @Constant, @Bindable, @BindSettings for details on the StrataCode annotations).

The swing integration provides examples of both annotation layers and the wrapper layer. Annotation layers are called "meta" by convention.

Annotation Layers

An annotation layer allows you to add annotations to pre-compiled Java classes without generating a new type or wrapper class in the system. The annotations affect how the pre-compiled class is used.

For example, the AWT annotation layer is quite simple. It annotates the classes java.awt.Point and java.awt.Dimension. These are objects which hold the x, y, width and height properties in awt. But since they do not send PropertyChange events, StrataCode can't monitor their changes. Instead, the swing system adds binding events for the location and size parent objects. Ordinarily StrataCode warns you about binding expressions to properties which do not fire events. To avoid these, the awt/meta layer marks these properties constant from StrataCode's perspective with the @Constant annotation. StrataCode will not let you set their values and won't give you a warning about the lack of a binding event when you use them in a binding expression.

Here's the awt.meta layer definition:

package java.awt;

awt.meta {
   annotationLayer = true;
}

and the files which annotate the Point and Dimension classes:

file: awt/meta/Point.sc
Point {
   override @Constant x;
   override @Constant y;
}

file: awt/meta/Dimension.sc
Dimension {
   override @Constant width;
   override @Constant height;
} 

The swing layer has its own annotation layer:

file: swing/meta/meta.sc
package javax.swing;

public swing.meta extends awt.meta { codeType = sc.layer.CodeType.Framework; codeFunction = sc.layer.CodeFunction.UI;

annotationLayer = true;

compiledOnly = true; // This layer does not run in dynamic mode }

An annotation layer can also set the @Component annotation on any class. This permits you to use the component initialization semantics without adding a wrapper layer of classes. Swing classes like JMenu, which do not require a wrapper class for data binding events, are defined in the annotation layer to avoid creating a wrapper class. In this example, anyone using a regular JMenu class will initialize it with component semantics using the specified code templates.

file: swing/meta/JMenu.sc
@Component
@CompilerSettings(objectTemplate="javax.swing.JComponentInit", 
                  newTemplate="javax.swing.JComponentNew", 
                  dynChildManager="sc.swing.SwingDynChildManager")
JMenu {

}

The Swing Core Layer

The Swing core layer injects data binding into Swing using simple wrapper classes for most components. A few properties are added and event listeners fire change events.

Here's the layer definition file:

file: swing/core/core.sc
package sc.swing;

import java.awt.Point;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.Container;
import java.awt.Font;
import java.awt.Color;
import java.awt.BorderLayout;
import java.awt.Insets;
import java.awt.Cursor;

import java.awt.Window;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.event.MouseListener;

import java.util.Enumeration;

import javax.swing.text.StyledDocument;
import javax.swing.ImageIcon;
import javax.swing.Icon;
import javax.swing.BorderFactory;

import javax.swing.UIManager;
import javax.swing.SwingUtilities;

import javax.swing.JRadioButtonMenuItem;
import javax.swing.JMenu;
import javax.swing.JMenuBar;

import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JDialog;
import javax.swing.JPopupMenu;
import javax.swing.AbstractButton;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.ListSelectionModel;
import javax.swing.JTabbedPane;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;

import sc.swing.*;

swing.core extends swing.meta, util {
   codeType = sc.layer.CodeType.Framework;
   codeFunction = sc.layer.CodeFunction.UI;

   compiledOnly = true; // No real reason to make this layer dynamic ever and something about Main.sc seems to make it not work

   void init() {
      addProcess(sc.layer.ProcessDefinition.create("Desktop"));
   }

   public void start() {
      sc.layer.LayeredSystem system = getLayeredSystem();
      sc.layer.LayerFileProcessor resourceFileProcessor = new sc.layer.LayerFileProcessor();

      resourceFileProcessor.prependLayerPackage = true;
      resourceFileProcessor.useSrcDir = false;
      resourceFileProcessor.useClassesDir = true;

      registerFileProcessor(resourceFileProcessor, "png");
      registerFileProcessor(resourceFileProcessor, "gif");
      registerFileProcessor(resourceFileProcessor, "jpg");

      sc.lang.DefaultAnnotationProcessor mainInitProc = new sc.lang.DefaultAnnotationProcessor();
      mainInitProc.typeGroupName = "mainInit";
      mainInitProc.validOnField = false;
      mainInitProc.validOnObject = true;
      mainInitProc.createOnStartup = true;

      registerAnnotationProcessor("sc.swing.MainInit", mainInitProc);

      if (activated) {
         system.addTypeGroupDependency("Main.sc", "sc.swing.Main", "mainInit");
      }
   }

}

Processing is added for resource files with the png and gif suffix. These files are copied into the build dir unless overridden replaced in a subsequent layer.

The MainInit annotation is defined. All classes tagged with @MainInit are put into a type group called mainInit. This type group is evaluated in the code template for the Main.sc class. For incremental builds, a dependency is added so Main.sc is regenerated if elements are added or removed from the type group.

These imports are exposed both to classes in the layer and for users of the layer without an explicit import. A subsequent layer could choose to not inherit imports at all with inheritImports=false. If you want to use imports internally only, not expose them to extended layers, set exportImports=false.

The wrapper class for a primitive component sets the @Component attribute, and triggers a change event for the "preferredSize" property when the text changes:

file: swing/core/JLabel.sc
import sc.bind.*;
import sc.type.IBeanMapper;
import sc.type.TypeUtil;

@Component
public class JLabel extends javax.swing.JLabel implements ComponentStyle {
   public static IBeanMapper textProp = TypeUtil.getPropertyMapping(JLabel.class, "text");

   @Bindable(manual = true)
   public void setText(String text) {
      super.setText(text);
      Bind.sendEvent(IListener.VALUE_CHANGED, this, SwingUtil.preferredSizeProp);
      Bind.sendEvent(IListener.VALUE_CHANGED, this, textProp);
   }

   @Bindable
   public void setIcon(Icon icon) {
      super.setIcon(icon);
      Bind.sendEvent(IListener.VALUE_CHANGED, this, SwingUtil.preferredSizeProp);
   }

   override @Bindable(manual=true) preferredSize;
   override @Bindable size;
   override @Bindable location;
   override @Bindable visible;
   override @Bindable foreground;
   override @Bindable background;
}

The JPanel class associates template objects which add sub-objects as children. By default, data binding is used so the default layout is also set:

file: swing/core/JPanel.sc
@Component
@CompilerSettings(objectTemplate="javax.swing.JComponentInit", newTemplate="sc.swing.JComponentNew")
public class JPanel extends javax.swing.JPanel {
   layout = null;
}

The JComponentInit template set via CompilerSettings is used to define a code snippet inserted into the declaring class when an object tag is used of that type. It is passed an object which contains all of the properties you need to evaluate. Here's the template for the swing component:

file: swing/meta/JComponentInit.sctd
/** 
 * Snippet to be inserted for each object definition which extends the swing JComponent class
 * Accumulates the children objects and adds them.
 */
<% if (!overrideField && !overrideGet) { %>
   <%=fieldModifiers%> <%=variableTypeName%> <%=lowerClassName%>;
<% } %>
<%=getModifiers%> <%=variableTypeName%> get<%=upperClassName%>(boolean doInit) {
<% if (overrideGet) { %>
   <%=variableTypeName%> <%=lowerClassName%> = (<%=variableTypeName%>) super.get<%=upperClassName%>();
<% } %>
   if (<%=lowerClassName%> == null) {
      <%=lowerClassName%> = <% if (typeIsCompiledComponent) { %><%=typeClassName%>.new<%=typeBaseName%>(false)<% } 
                               else { %>new <%=typeName%>()<% } %>;
     <% if (overrideGet) { %>
         set<%=upperClassName%>(<%=lowerClassName%>);
     <% } %>
     <%=lowerClassName%>.preInit();
     <%=getDynamicTypeDefinition(lowerClassName, 2)%><%=propertyAssignments%>
     java.util.List _children = (java.util.List) <%=lowerClassName%>.getClientProperty("sc.children");
     if (_children == null)
         _children = java.util.Arrays.asList(<%=childrenNames%>);
     for (Object _child:_children) {
        sc.swing.SwingUtil.addChild(<%=lowerClassName%>, _child);
     }
     if (doInit) {
       <%=lowerClassName%>.init();
       <%=lowerClassName%>.start();
     }
     return <%=returnCast%><%=lowerClassName%>;
  }
  else return <%=returnCast%><%=lowerClassName%>;
}
@sc.obj.TypeSettings(objectType=true)
<%=getModifiers%> <%=variableTypeName%> get<%=upperClassName%>() { return get<%=upperClassName%>(true); }

See sc.lang.java.ObjectDefinitionParameters for the object passed to the init or new templates.

The JComponentNew.sctd file is used when you mark a class with @Component. It generates a newX method for each constructor and rewrites all uses of the constructor for this class to use newX instead.

The rest of the swing components are mostly thin wrappers adding binding properties where necessary. In one case, it calls invalidate/validate after a size property has changed as swing failed to detect that itself.

To integrate data binding with the Swing event thread, the listeners will use sendDelayedEvent, and processStatement to put the event delivery and script commands back on the swing thread.

Android View Example

Android View components do not have a zero argument constructor. Instead they must be created with a Context parameter. To support this pattern with the "object" operator, you can use the enclosing root level Activity object as the context for any children created inside of it.

First, associate the View class with the ViewObj code template:

file: android/meta/view/View.sc
@Component
@CompilerSettings(objectTemplate="android.view.ViewObj", 
                  newTemplate="android.view.ViewNew",
                  propagateConstructor="android.content.Context")
View {}

The propagateConstructor parameter specifies that the constructor which takes the Context parameter is propagated to any subclasses unless those subclasses define that constructor explicitly. This eliminates the need to have to manually copy a trivial constructor into each class.

Then specify a code template which uses the rootName variable as the first parameter to the constructor:

file: android/meta/view/ViewObj.sctd
/** 
 * Snippet to be inserted for each object definition which extends the android View class
 * Must be inside of an Activity component.
 */
<% if (rootName == null) 
      throw new IllegalArgumentException("Objects of type: " + typeName + 
                                         " must be children of an Activity/Service"); %>
<% if (!overrideField && !overrideGet) { %>
   <%=fieldModifiers%> <%=variableTypeName%> <%=lowerClassName%>;
<% } %>
<%=getModifiers%> <%=variableTypeName%> get<%=upperClassName%>(boolean doInit) {
<% if (overrideGet) { %>
   <%=variableTypeName%> <%=lowerClassName%> = super.get<%=upperClassName%>();\
<% } %>
   if (<%=lowerClassName%> == null) {
   <%=lowerClassName%> = <% 
      if (typeIsComponentClass) { 
          %><%=typeClassName%>.new<%=typeBaseName%>(false,<%=rootName%>)<% 
      } else { 
          %>new <%=typeName%>(<%=rootName%>)<% 
      } %>;
<% if (overrideGet) { %>
      set<%=upperClassName%>(<%=lowerClassName%>);
<% } %>
     <%=lowerClassName%>.preInit();
     <%=propertyAssignments%>
     if (doInit) {
       <%=lowerClassName%>.init();
       <%=lowerClassName%>.start();
     }
     return <%=lowerClassName%>;
  }
  else return <%=lowerClassName%>;
}
<%=getModifiers%> <%=variableTypeName%> get<%=upperClassName%>() { 
   return get<%=upperClassName%>(true); 
}

Main Settings

One of the sad features of Java is that after you compile your program, you are left with the frustrating task of trying to find the right class path and main class to run it. StrataCode helps by supporting the @MainSettings annotation, typically set on a framework class so it's transparent to the application developer. It generates a ".sh" script to run your application. It also will generate a "main.jar" file which you can use to package it up for you. The script works for either compiled or interpreted execution. You can enable debugging, set JVM parameters, or produce a jar file via MainSettings.

The swing layer's main provides an example of how to use this feature:

file: swing/core/Main.sc
import sc.bind.Bind;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import sc.obj.MainSettings;
/** 
 * A simple main you can use for your swing components.
 * Children of this object are automatically instantiated at startup
 */
@Component
@CompilerSettings(objectTemplate="sc.swing.MainInitTemplate")
public object Main {
   /** Create and register a binding manager to ensure events are delivered only on the swing thread */
   object bindingManager extends SwingBindingManager {
   }
   object statementProcessor extends SwingStatementProcessor {
   }

   @MainSettings(produceScript=true,execName="swingmain")
   public static void main(String[] args) {
      //Schedule a job for the event dispatching thread:
      //creating and showing this application's GUI.
      SwingUtilities.invokeLater(new Runnable() {
            public void run() {
               // Need to update the event handling thread with the current class loader so JPA and things can use it to find
               // resources
               ClassLoader cl = sc.dyn.DynUtil.getSysClassLoader();
               if (cl != null)
                  Thread.currentThread().setContextClassLoader(cl);

               Main main = Main; // Referencing the main object will create it and kick everything off
               // If any dynamic types are registered with the @MainInit when we start, grab them as well
               // Actually, we'll compile in any such references using dynamic new calls
               //Object[] dynInitObj = sc.dyn.DynUtil.resolveTypeGroupMembers("mainInit");
            }
        });
    }
}

Also check out how StrataCode's own compiler's main is defined (since StrataCode builds StrataCode).

class LayeredSystem {
   //...
   @MainSettings(produceJar=true,produceScript=true,execName="sc",debug=true)
   public static void main(String[] args) {
   }
}