StrataCode Framework Development

StrataCode lets you build powerful frameworks - those that can transform large, complex Java projects using deep code awareness and an easy mechanism to perform targeted transformations to improve quality, functionality, performance, code modularity, and code configurability.

StrataCode can grok an entire large Java project from source code, or a mix of source and Java binaries (jar/class). It provides a complete type-aware API for navigating types, dependencies, attributes, whether a type is defined from a .class file or source. It exposes just the right set of hook points for letting you add annotation processors, or process types which inherit metadata from types they extend. You can mix code into the class definitions, convert fields to get/set methods, add wrapper methods that inject events into a setX method, support data binding, enable component features, or write custom code processors that run on selected types. There are APIs which let a framework developer make natural API calls to make incremental changes to a Java language model which are then incrementally applied to the file.

To get started with Framework Development using StrataCode, first read about the Build and Packaging Runtime

If you want to dig into manipulating source code yourself, read about the Parselets apis.

If you just want to use the APIs, without layers, go straight to the simpleParser example.

Framework layers are just regular layers that tend to do more:

  • Default imports of classes used by this framework
  • Set properties to control how code generation is performed for this runtime
  • Add annotation and scope processors - to control generated code for specifically marked types, properties, and methods
  • Annotate compiled classes for which we do not have source to control how those classes are used in code-processing
  • Specify runtime/process restrictions - i.e. runs in java or js only, or only runs in a specific named process.

Default Imports

Imports in your layer are made available to all classes in extended layers, unless the downstream layer sets inheritImports = false or the upstream layer sets exportImports = false.

Similarly the package of the base layer is inherited by the sub-layer unless the sub-layer sets its own package, or the base layer specifies exportPackage = false, or the sub-layer specifies inheritPackage = false.

Set properties

The layer and layered system classes support a variety of features that let a given framework layer customize it's environment. This includes adding file processors, specifying which processes or runtimes will be synchronized, etc. You can add post build commands, test frameworks and more. These are all covered in build and packaging.

Annotation and Scope Processors

One of the more powerful framework development patterns allows you to attach special code-processing behavior to types, fields, properties, methods, etc. using Java annotations.

Framework layers may register one or more annotation processors using the DefaultAnnotationProcessor class. When a specific annotation is used on a class, you can mix a code-template into that class, or write code to modify the AST objects to transform the type, property, or method to do whatever you want. You can accumulate all instances of a given class or object into a 'type group' - which can then be enumerated in some data structure elsewhere in the system (for example, generating an XML file formatted with metadata for all persistent types). An annotation handler can cause types to be initialized or created when the application starts. It can append an interface to types marked with the annotation.

One of the most significant ways you can alter a type or field is to specify a 'scope' to control its lifecycle, or how it's value is retrieved. While scopes use the same underlying base-class as annotation processors, DefinitionProcessor, they are organized around changing how an instance of a type or property value is resolved and so have a class of their own: BasicScopeProcessor. Because the code transformation performed by 'scopes' follows a specific pattern, and is a structural change to the behavior, scopes are normally specified with the 'scope' keyword added by StrataCode. You can also specify a scope using the Scope annotation, or use the Scope annotation to mark types or fields that were tagged by the scope keyword.

Here's the servlet/core layer definition file which defines the @URL annotation handler and scope handlers for window, and appSession scope. It also managed type groups for all of the servlets.

file: servlet/core/
package sc.servlet;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import sc.lang.html.IPage;
import sc.lang.html.UserAgentInfo;

import sc.servlet.Context;
import sc.servlet.UploadPage;
import sc.servlet.DownloadPage;
import sc.servlet.JSONPage;

public servlet.core extends webApp, meta, html.core {
   compiledOnly = true;
   hidden = true;

   codeType = sc.layer.CodeType.Framework;

   public void init() {
      // Split this layer and it's sublayers out into a new process using the default 'java' runtime
      addProcess(ProcessDefinition.create(layeredSystem, "Server", "java", true));

      if (activated) {
         // Turns on URL access to the layered system - only for the active layer
         layeredSystem.serverEnabled = true;

   public void start() {

      // Like request but where instances are stored in the browser's session sessionScope = new"session");
      sessionScope.validOnClass = true;
      sessionScope.validOnField = false;
      sessionScope.validOnObject = true;
      sessionScope.includeScopeAnnotation = true;
      sessionScope.needsField = false;
      sessionScope.customResolver = 
          "      javax.servlet.http.HttpSession _session = sc.servlet.Context.getCurrentSession();\n" +
          "      if (_session == null) return null;\n" +
          "      <%= variableTypeName %> _<%= lowerClassName %> = (<%= variableTypeName %>) _session.getAttribute(\"<%= typeClassName %>\");\n";
      sessionScope.customSetter = 
          "      _session.setAttribute(\"<%= typeClassName %>\", _<%= lowerClassName %>);\n";
      registerScopeProcessor("session", sessionScope);

      // Like session but stored per-window, per-session windowScope = new"window");
      windowScope.validOnClass = true;
      windowScope.validOnField = false;
      windowScope.validOnObject = true;
      windowScope.includeScopeAnnotation = true;
      windowScope.needsField = false;
      windowScope.customResolver = 
          "      sc.obj.ScopeContext _ctx = sc.servlet.WindowScopeDefinition.getWindowScope();\n" +
          "      if (_ctx == null) return null;\n" +
          "      <%= variableTypeName %> _<%= lowerClassName %> = (<%= variableTypeName %>) _ctx.getValue(\"<%= typeClassName %>\");\n";
      windowScope.customSetter =
          "      sc.servlet.Context.getWindowScope(true).setValue(\"<%= typeClassName %>\", _<%= lowerClassName %>);\n";
      registerScopeProcessor("window", windowScope);

      // Like session but stores objects per-url, per-session appSessionScope = new"appSession");
      appSessionScope.validOnClass = true;
      appSessionScope.validOnField = false;
      appSessionScope.validOnObject = true;
      appSessionScope.includeScopeAnnotation = true;
      appSessionScope.needsField = false;
      appSessionScope.customResolver = 
          "      sc.obj.ScopeContext _ctx = sc.servlet.AppSessionScopeDefinition.getAppSessionScope();\n" +
          "      if (_ctx == null) return null;\n" +
          "      <%= variableTypeName %> _<%= lowerClassName %> = (<%= variableTypeName %>) _ctx.getValue(\"<%= typeClassName %>\");\n";
      appSessionScope.customSetter = 
          "      _ctx.setValue(\"<%= typeClassName %>\", _<%= lowerClassName %>);\n";
      registerScopeProcessor("appSession", appSessionScope);

      sc.lang.DefaultAnnotationProcessor urlProc = new sc.lang.DefaultAnnotationProcessor();
      // Adds a static code snippet to register the page when you annotate a class with @URL.  If we happen to register an inner class the addPage still goes on the parent type
      urlProc.staticMixinTemplate = "sc.servlet.URLMixinTemplate";
      urlProc.validOnField = false;
      urlProc.validOnClass = true;
      urlProc.validOnObject = true;
      urlProc.initOnStartup = true;
      urlProc.typeGroupName = "URLTypes";
      urlProc.inherited = true; // Include any sub-type which has URL in the type group
      urlProc.skipAbstract = true; // Don't include any abstract classes or templates with abstract="true"
      registerAnnotationProcessor("sc.html.URL", urlProc);

      // Since the new sc.html.URL processor will include dependencies on sc.servlet in this package, if html.core gets compiled, we need to be compiled along
      // with it if we are in the same stack.


CompilerSettings is an annotation you can set on a class to control how code-processing treats that class in a general way. You can specify an 'objectTemplate' on any base-class to define the getX method used to retrieve that object instance. You can specify a 'newTemplate' which specifies either the constructor or newX method used for when this class is created explicitly via a new expression. Other templates let you mix in code to the instance or static sections. For classes which have a non-default constructor that frequently looks the same - i.e. pass the arguments through to the super constructor, you can specify the arg list with propagateConstructor so sub-classes get the simple constructor automatically, unless it's specified manually.

Remember that CompilerSettings is inherited, so that when you set it on a base class, any sub-class of that base-class will pick up the settings of the base-class unless it overrides them with it's own CompilerSettings.

Add Data Binding

Sometimes a framework layer needs to wrap a framework to inject code so we can use that class with data binding. In other cases, you can add this support via an annotation layer. An annotation layer can modify classes for which we do not have the source, but in a restricted way. Certain annotations can be set on compiled types in the annotation layer which do not alter the type's source code, but instead change how StrataCode processes any references to that type. For example, you can suppress binding warnings, add some mix-in code to each sub-class of the base class, mark a property as read-only and specify templates to be used for all object, or component references to this base-class.

Annotation Layers

An annotation layer allows you to add annotations to pre-compiled Java classes (i.e. where StrataCode does not have the source) without generating a new type or wrapper class in the system. The annotations affect how the pre-compiled class is used by subclasses, or in generated code. By convention, annotation layers are named 'meta' - short for metadata.

For example, the awt.meta annotation annotates the classes java.awt.Point and java.awt.Dimension. These classes hold the x, y, and width, height properties in awt. But since they do not send PropertyChange events, StrataCode can't monitor their changes. Instead, swing components have properties 'location' and 'size' to store Point and Dimension instances. The swing components add binding events to these properties. By default, StrataCode warns you in this situation - if you bind to button.location.x it would warn you that 'x' is not bindable. To avoid these warnings, the awt.meta layer marks these properties constant from StrataCode's perspective with the @Constant annotation. StrataCode will give a compile error if your code attempts to set the value of the @Constant property and suppresses the warning when you bind to it.

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 {
   override @Constant x;
   override @Constant y;
file: awt/meta/
Dimension {
   override @Constant width;
   override @Constant height;
The swing layer has its own annotation layer:
file: swing/meta/
package javax.swing;

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

   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 {


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/
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 javax.swing.ImageIcon;

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;

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

   void init() {
      addProcess(ProcessDefinition.create(layeredSystem, "Desktop", "java", false));

   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;
      // Using the 'createOnStartup' flag, types with this @MainInit annotation are placed into the BuildInfo initTypes which code-templates can use
      // to create them at the appropriate time.
      mainInitProc.createOnStartup = true;

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

      addTypeGroupDependency("", "sc.swing.Main", "mainInit");


There are a few things to notice about this layer definition:

  • Processing is added for resource files with the png and gif suffix. These files are copied into the build dir unless replaced by a file with the same name/path in a subsequent layer.
  • A DefaultAnnotationProcess for the @MainInit annotation is registered. This causes all classes tagged with @MainInit to be placed into a type group called mainInit. This type group is evaluated in the CompilerSettings.objectTemplate set on the object (see below). For incremental builds, a dependency is added so is regenerated if elements are added or removed from the type group.
  • A large set of 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.

Here's a simple swing component sc.swing.JLabel which extends javax.swing.JLabel:

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

/** Wrapper for javax.swing.JLabel to make it a bindable StrataCode component. */
public class JLabel extends javax.swing.JLabel implements ComponentStyle {
   public static IBeanMapper textProp = TypeUtil.getPropertyMapping(JLabel.class, "text");

    *  Overrides the swing setText method to fire change events for both preferredSize and text.
    *  No need for automatic binding events so turn that off.
   @Bindable(manual = true)
   public void setText(String text) {
      Bind.sendEvent(IListener.VALUE_CHANGED, this, SwingUtil.preferredSizeProp);
      Bind.sendEvent(IListener.VALUE_CHANGED, this, textProp);

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

   /** Binding events are already sent above so mark it as Bindable but turn off code-gen */
   override @Bindable(manual=true) preferredSize;

   /** Will code-gen a setX method that calls super.setX and sends a change event */
   override @Bindable size;
   override @Bindable location;
   override @Bindable visible;
   override @Bindable foreground;
   override @Bindable background;

Here is the sc.swing.JPanel class which extends javax.swing.JPanel to show how to wrap a parent component:

file: swing/core/
/** Wraps javax.swing.JPanel to make it a bindable StrataCode component, where child objects become managed child widgets */
@CompilerSettings(objectTemplate="javax.swing.JComponentInit", newTemplate="javax.swing.JComponentNew", dynChildManager="sc.swing.SwingDynChildManager")
public class JPanel extends javax.swing.JPanel {
   static sc.type.IBeanMapper sizeProperty = sc.type.TypeUtil.getPropertyMapping(JPanel.class, "size");
   static sc.type.IBeanMapper locationProperty = sc.type.TypeUtil.getPropertyMapping(JPanel.class, "location");

   layout = null;

   override @Bindable size;
   override @Bindable location;
   override @Bindable visible;

   override @Bindable preferredSize;
   override @Bindable font;

   public void setBounds(int x, int y, int w, int h) {
      boolean sizeChanged = this.width != w || this.height != h;
      boolean locChanged = this.x != x || this.y != y;
      super.setBounds(x, y, w, h);
      if (sizeChanged)
         sc.bind.Bind.sendEvent(sc.bind.IListener.VALUE_CHANGED, this, sizeProperty);
      if (locChanged)
         sc.bind.Bind.sendEvent(sc.bind.IListener.VALUE_CHANGED, this, locationProperty);

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) {<%=beforeNewObject%>
      <%=variableTypeName%> _<%=lowerClassName%> = <% if (typeIsCompiledComponent) { %><%=typeClassName%>.new<%=typeBaseName%>(false<%=nextConstructorParams%>)<% }
                               else { %>new <%=typeName%>(<%=constructorParams%>)<% } %>;
      <%=lowerClassName%> = _<%=lowerClassName%>;
      <% if (overrideGet) { %>
      <% } %>
      <%=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) {
      return <%=returnCast%>_<%=lowerClassName%>;
   else {
      <%=variableTypeName%> _<%=lowerClassName%> = <%=returnCast%><%=lowerClassName%>;<%=accessHook%>
      return _<%=lowerClassName%>;
<%=getModifiers%> <%=variableTypeName%> get<%=upperClassName%>() { return get<%=upperClassName%>(true); }

See 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

The android framework is an interesting example to show how to use the object operator with classes that do not have a zero argument constructor. For android, the Android View class constructor always takes a Context parameter. But it's easy to get the Context from a parent object. So the case where you are creating a View object inside of an enclosing Activity or Service is well defined, but we have to change the code templates we use.

First, associate the View class with the ViewObj objectTemplate:

file: android/meta/view/
@CompilerSettings(// object and new templates to handle passing the context parameter to the constructor
                  // Auto-create constructor with this signature to call super(context) unless one is there
View {}
Here's the 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)  // rootName is the object name of the root object when this is a child - in this case, a ref to the outer-most Activity in which we are defined
      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) {
      <%=variableTypeName%> _<%=lowerClassName%> = <% 
      if (typeIsComponentClass) { 
      } else { 
          %>new <%=typeName%>(<%=rootName%>)<% 
      } %>;
      <%=lowerClassName%> = _<%=lowerClassName%>;
<% if (overrideGet) { %>
<% } %>
      if (doInit) {
      return _<%=lowerClassName%>;
   else {
      <%=variableTypeName%> _<%=lowerClassName%> = <%=returnCast%><%=lowerClassName%>;<%=accessHook%>
      return <%=lowerClassName%>;
<%=getModifiers%> <%=variableTypeName%> get<%=upperClassName%>() { 
   return get<%=upperClassName%>(true); 

Main Settings

The @MainSettings annotation lets you declaratively configure one or more main methods to be run when the stack of layers is run. This lets the scc command run your program immediately after compiling it, or generates a shell or bat script to run the program on its own.

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

file: swing/core/
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
public object Main {
   /** Access to the args array provided to the main method (if any) */
   public static String[] programArgs;
   /** Create and register a binding manager to ensure events are delivered only on the swing thread */
   object bindingManager extends SwingBindingManager {
   object statementProcessor extends SwingStatementProcessor {

   public static void main(String[] args) {
      programArgs = 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)

               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");

Another good example of @MainSettings is StrataCode's own main which produces the 'scc' command when it's used to compile itself:

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