| ### The jampack Composition Tool

jampack is a tool to compose .jak files that define a single refinement chain. The Composition document presents the big picture of how and where jampack is used, the syntax and semantics of .jak files, and how jampack differs from the mixin composition tool. This document discusses detailed use and features of jampack.

- command-line invocation
- interface composition
- class composition
  - key sorting
  - variable composition
  - method composition
  - constructor composition
- state machine composition
- local declarations

General rules of formatting and composition that are shared by mixin and jampack are described in the Composition document.

## Command-Line Invocation

To call jampack from the command line:

>     > jampack

>     Usage: jampack [options] baseFile extensionFile1 extensionFile2 ...
>     Options: -t type-sort interface declarations
>              -k key-sort class field declarations
>              -a <layerName> name of layer to generate
>              -f <fileName> name file to generate

where:

- option -t is for type sorting
- option -k is for key sorting (ignored if interfaces are being refined)
- if option -a is selected, you must also supply layerName. This is the name of the composite layer that is produced.
  By default, the layer name is the same as the root file.
- if option -f is selected, you must also supply fileName. This is the name of the file in which the composite layer is to be stored.
  If f is not selected, output is sent to standard out.
- files to be composed are listed base-file first. So for the composition A(B(C)), the files are listed C B A on the jampack command line.

## Interface Composition Rules

The rules for interface composition that are specific to jampack are:

- modifiers of the root and refinement are merged (without duplication).
- extends lists are merged (without duplication).
- bodies are merged (without duplication)
- with option -t, list of field declarations are type sorted (constants first, methods second).

An example root interface and refinement are:

>     layer Iroot1;

>     import x.y.z.*;

>     interface MyInt extends FooInterface {
>        int Silent = 0;
>        void foo() throws AFit;
>        SomeType bar( int x );
>     }

>     layer Iext1;

>     import java.io.Serializable;

>     public transient refines interface MyInt extends yyyInterface {
>        int Terse = 2;
>        void foo() throws SomeException;
>        int increment( int i );
>     }

The result of their composition by jampack is shown below. Highlighted in yellow are additions made by the MyInt refinement.

>     layer Foo;

>     import x.y.z.*;
>     import java.io.Serializable;

>     public transient interface MyInt extends FooInterface, yyyInterface {
>        int Silent = 0;
>        int Terse = 2;
>        void foo() throws AFit, SomeException;
>        SomeType bar( int x );
>        int increment( int i );
>     }

> Note: jampack merges modifier lists. That is, the definition of MyInt in IRoot1 has no modifiers and the refinement in Iext1 adds the public and transient modifiers. mixin does not merge modifier lists. So if you want compatibility between mixin and jampack, avoid using modifiers in refinements.

Type sorting is a linear algorithm that sorts interface declarations. It is only present in jampack and is invoked by the -t option command-line option. To see why type sorting is useful, consider the result if type sorting is not used (see below): it is a jungle of definitions that are hard to understand. Type sorting collects all variable definitions together in one spot and method definitions in another, and makes a small contribution to generated code beautification.

>     public transient interface MyInt extends FooInterface, yyyInterface {
>         int Silent = 0;
>         void foo() throws AFit, SomeException;
>         SomeType bar( int x );
>         int Terse = 2;
>         int increment( int i );
>     }

#### Exercise

Create separate files A.jak and B.jak to hold the above definitions. Compose them into C.jak by the command:

>     > jampack -t -a Foo A.jak B.jak > C.jak

Remove -t to turn off type sorting.

Caveat. Note: the file naming convention used in this example isn't typical. Usually A.jak would be a file in the IRoot1 layer (i.e., it would have the pathname IRoot1/MyInt.jak) and Iext1.jak would be a file in the Iext1 layer (i.e., it would have the pathname Iext1/MyInt.jak). So a more typical invocation would be:

>     > jampack -a Foo IRoot1/MyInt.jak IIext1/MyInt.jak > C.jak

## Class Composition Rules

The rules of class composition that are specific to jampack are:

- modifiers of the base and refinement are merged (without duplication).
- implements clause lists are merged (without duplication).
- bodies are merged.
- with option -k, list of field declarations are key sorted.

The following sections discuss how jampack composes .jak files. The ideas actually are general and apply to mixin as well. So it is recommended that you read both documents.

- composing variables
- composing methods
- composing constructors

An example of a base class file and an refinement class file are:

>     layer Ctop;

>     import jakarta.util.*;

>     class top {
>        static int i,j;
>        top() { ii = 5; }
>        void foo(float x, float y) { /* do something */ }
>        float bar( float x ) { /* do something */ }
>     }

>     layer Cmid;

>     import AnotherPackage;

>     refines class top implements java.io.Serializable, xxx {
>        static int k;
>        static { j = 5; }

>        top(float x) { /* do something */ }

>        float foobar() { Super(float).bar(4.0); Super(float,float).foo(0, 0); }

>        public void foo( float x, float y ) { /* something more */ }
>     }

The result of their jampack composition is shown below. Highlighted in yellow are additions made by the refinement:

>     layer Foo;

>     import jakarta.util.*;
>     import AnotherPackage;

>     class top implements java.io.Serializable, xxx {
>        static int i,j;
>        static int k;
>        static { j = 5; }

>        top() { ii = 5; }

>        top(float x) { /* do something */ }

>        float bar( float x ){ return bar$$Ctop( x ); }
>        final float bar$$Ctop( float x ) { /* do something */
>        final void foo$$Ctop(float x, float y) { /* do something */ }

>        public void foo( float x, float y ) { /* something more */ }

>        float foobar() { bar$$Ctop(4.0); foo$$Ctop(0, 0); }
>     }

Key sorting is a technique much like type sorting. The body of a class has different kinds of entities -- variable declarations, method declarations, etc. Key sorting is similar to type sorting in that all entities of a single type (initialization blocks, variable declarations, methods) are grouped together. Key sorting goes beyond this to sort methods. In particular, we expect that there will be many "variations" of a single method, such as bar, bar$$Ctop, etc. Key sorting groups all of these related methods together, as shown above. (It does this by assigning a key to each method and sorting the methods in key order. Hence the name "key sort"). If key sorting is not used, an unintelligible jungle of declarations is produce. (Try the above example without the -k option to see).

>     class top implements java.io.Serializable, xxx {
>        static int i,j;
>        static int k;
>        top() { ii = 5; }
>        top(float x) { /* do something */ }
>        final void foo$$Ctop(float x, float y) { /* do something */ }
>        float bar( float x ){ return bar$$Ctop( x ); }
>        final float bar$$Ctop( float x ) { /* do something */ }
>        float foobar() { bar$$Ctop(4.0); foo$$Ctop(0, 0); }
>        public void foo( float x, float y ) { /* something more */ }
>        static { j = 5; }
>     }

There are special language features, such as Super, that you need to understand and use when defining class refinements. We explain Super and how these files are produced in the following sections.

#### Exercise

Create separate files top.jak and mid.jak to hold the definitions above. Compose them into combined.jak by the command:

>     > jampack -k -a Foo top.jak mid.jak > combined.jak

Remove the -k option to turn off key sorting. Don't forget the caveat.

### Rules for Variable Composition

There is no notion of variable refinement in Java. It is one thing to override a method; it makes no sense to override a variable. Whenever a parent class defines a variable and a refinement class attempts to define the same variable, an error is reported by jampack.

This begs the question of inadvertent capture. What if a temporary variable x is defined in the root class and a different variable, also named x, is defined in the refinement class? As mentioned above, jampack will complain. The clashing of names for temporary variables should be handled automatically. In a later section, we show how local declarations avoid inadvertent capture.

### Rules for Method Composition

You may have noticed that composing classes is a lot more complicated than composing interfaces. The difficulty rests on the ability of methods of an refinement class to call arbitrary methods of their "superclass" or rather, "parent". And part of the difficulty arises because jampack is a relatively simple preprocessor which doesn't have full type information about a program it is composing.

You know that a subclass can call method foo() of its superclass by invoking super.foo(). By analogy, a class refinement can call method foo() of its super refinement by invoking Super().foo(). Because jampack is a preprocessor that does not type check programs, if a refinement calls method of its parent with signature foo(int, float, String), it does so by invoking Super(int,float,String).foo(5, 3.4, "me"). The token Super is a reserved keyword. Super is always followed by the type signature of the method that is to be invoked, and is the way the type signature of a "base-class" method is conveyed to jampack.

There are four rules that are specific to extending base-class methods. The actions taken depend on the following conditions:

- A method of the parent class (or parent method) is overridden if there exists a method of the refinement class (or refinement method) with an identical signature. (This is the usual notion of overriding).
- A parent method is referenced if the method is invoked in the refinement class.

If a parent method is:

- overridden and referenced -- The original method name is mangled or "uniquified" -- "name" becomes "name$$layerName". This is useful as programmers will know from which layer the method arose. The refinement method is rewritten to reference this local method.

| Before Composition | After Composition in Composite Class
Parent class |
    void foo( ) {
       /* do something */
    }
    final void foo$$baseAspect() {
       /* do something */
    }
    void foo( ) {
       foo$$baseAspect();
       // something more
    }
Refinement class |
    void foo( ) {
       Super( ).foo();
       // something more
    }

- overridden and not referenced -- the parent method is discarded, as it is not referenced and is replaced by a refinement method.

| Before Composition | After Composition in Composite Class
Parent class |
    void foo( ) {
       /* do something */
    }
    void foo( ) {
       // something more
    }
Refinement class |
    void foo( ) {
       // something else
    }

- not overridden and referenced -- This is the most complicated of rewrites. First, because the parent method was referenced, a local copy of it with a layer-mangled name is created. Second, a new parent method is created to refer to the local method thereby preserving the original parent method's semantics. Lastly, all references in the refinement are made to the local method.

| Before Composition | After Composition in Composite Class
Parent class |
    void foo( ) {
       /* do something */
    }
    final void foo$$parentAspect( ) {
       /* do something */
    }
    void foo( ) {
       foo$$parentAspect();
    }
    void bar( ) {
       foo$$parentAspect();
       // something more
    }
Refinement class |
    void bar( ) {
       Super().foo();
       // something more
    }

- not overridden and not referenced -- leave base method as is.

There is one rule for refinement methods: if an refinement method does not override a base method, it is merely added to the composite class.

| Before Composition | After Composition in Composite Class
Parent class |
    void biff( ) {
       // something here
    }
Refinement class |
    void biff( ) {
       // something here
    }

### Rules for Composing Constructors

There are all sorts of problems with constructors. There are so many that it is much safer NOT to allow constructors to be refined. This is the rule for both mixin and jampack. New constructors can be added by an refinement, but no existing constructor can be refined.

Constructors can be refined using the concepts already presented. The body of a constructor is moved into a method, which can be refined. Thus, instead of creating a superclass with a constructor and later extending the constructor via inheritance (left-hand- side of the table below), we rely on a design and coding technique to accomplish the same effect (right-hand-side of the table below). Below, foo is the name of a class and fooConstructor is the name of the method that contains the body of the foo constructor:

Inheritance Hierarchy | Refinement Chain Equivalent
    class foo {
       int a;
       foo(int a) {
          this.a = a;
       }
    }
    class foo {
       int a;
       void fooConstructor(int a) {
          this.a = a;
       }
       foo(int a) {
          fooConstructor(a);
       }
    }
    class subfoo extends foo {
       int b;
       foo(int a) {
          super(a);
          b = 0;
       }
    }
    refines foo {
       int b;
       void fooConstructor(int a) {
          Super(int).fooConstructor(a);
          b = 0;
       }
    }

#### Exercise

Create some example classes to compose. Follow the rules for designing constructors, methods, and variables. Remember that each file to compose must belong to an layer(i.e., have an layer declaration) with a unique name -- i.e., the name of the layer from which it belongs. Failure to follow these rules will result in incorrect code being generated.

## State Machine Composition Rules

State machine composition rules are:

- each file to be composed must belong to a separate layer -- i.e., the layer names for each file to be composed must be unique.
- an refinement cannot override or extend a previously defined state machine declaration (i.e., state, edge, exit method, enter method, etc.)
- composition is the union of the base and refinement specifications. (This is the reason for the above bullet: an refinement cannot redefine a state machine declaration originally specified in the base).

Consider the following root state machine and refinement:

> >     layer rootSm;

> >     State_machine root {
> >        Delivery_parameters( M m );
> >        Unrecognizable_state { ignore(m); }

> >        States g, h, i;

> >        Transition e1 : g -> h condition m!=null do { gh(); }
> >     }

> >     layer extSm;

> >     refines State_machine root {
> >        States j, k;

> >        Transition e3 : g -> j  condition m!=null do { Super(int).anotherAction(6); }
> >     }

jampack composes the above files to yield the specification below. The lines in yellow were added by the state machine refinement:

>     layer Foo;

>     State_machine root {
>        Delivery_parameters( M m );
>        Unrecognizable_state { ignore(m); }

>        States g, h, i;
>        States j, k;

>        Transition e1 : g -> h condition m!=null do { gh(); }

>        Transition e3 : g -> j condition m!=null do { super.anotherAction(6); }
>     }

Note that part of a root or refinement state machine specification is a set of data member, methods, etc. The rules for composing these are exactly the same as those for composing class declarations, explained above. So options like key-sorting are relevant.

#### Exercise

Create separate files root.jak and mid.jak to hold the above definitions. Compose them into result.jak by the command:

>     > jampack -tk -a Foo root.jak mid.jak > result.jak

Don't forget the caveat.

## Local Declarations

Local_Id declarations are used to avoid inadvertent capture. Consider the following root specification with local identifiers i, j, ii, jj, and foo.

>     layer Ctopp;

>     Local_Id i, jj, foo;

>     class topp {
>        static int i;
>        int jj;

>        void foo(float x, float y) { i = jj = x+y; }
>     }

Now consider an refinement that uses exactly the same identifiers locally:

>     layer Cmidd;

>     Local_Id i, jj, foo;

>     refines class topp {
>        static int i;
>        int jj;

>        void foo(float x, float y) { i = jj = x*y; }
>     }

jampack composes these two specifications to yield:

>     layer Foo;

>     class topp {
>        static int i$$Ctopp;
>        static int i$$Cmidd;
>        int jj$$Ctopp;
>        int jj$$Cmidd;

>        void foo$$Ctopp(float x, float y) { i$$Ctopp = jj$$Ctopp = x+y; }

>        void foo$$Cmidd(float x, float y) { i$$Cmidd = jj$$Cmidd = x*y; }
>     }

Note that jampack first assigns unique (mangled) names to local identifiers before composing specifications. It doesn't matter if jampack is composing classes, interfaces, state machines or whatever. The Local_Id feature works for them all, and it works the same way for mixin.

#### Exercise

Create separate files topp.jak and extp.jak to hold the definitions above. Compose them into result.jak by the command:

>     > jampack -tk -a Foo topp.jak extp.jak > result.jak

Don't forget the caveat.

ATS Home Page

Copyright © Software Systems Generator Research Group. All rights reserved.
Revised: January 25, 2006.