begin layers method
  • CompClass -- compresses class extension hierarchies
  • end layers method
  • Class extension specifications
  • Specifying Classes and Class Extensions

    PJ expects a file containing a base class to have the following format:

    package <package-name>;
    [<import-statements>]
    <java-class-declaration>

    Note:

    An class extension file has the following format:

    package <package-name>;
    [<import-statements>]
    [<modifiers>] extends class <name> [ implements <interface-list> ] { [body] }

    Note:

    Rules of Composition

    Again, rules of composition are simple:

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

    package Ctop;
    
    import jakarta.util.*;
    
    class top {
       static int i,j;
       int ii,jj;
    
       static { i = 4; }
    
       top() {  ii = 5; }
       top(int rj) { jj = rj; }
    
       void foo(float x, float y) { /* do something */ }
    
       float bar( float x ) { /* do something more */ }
    }

    package Cmid;
    
    import AnotherPackage;
    
    extends class top implements java.io.Serializable, xxx {
       static int k;
       static { j = 5; }
    
       top(float x) { /* do something */ }
    
       float foobar() { Base(float).bar(4.0);
                        Base(float,float).foo(0, 0); }
    
       public void foo( float x, float y ) { /* something more */ }
    }

    The result of their composition is shown below.  Highlighted in yellow are additions made by the top extension and highlighted in blue are additions triggered by the extension:

    package Ctop;
    
    import jakarta.util.*;
    import AnotherPackage;
    
    class top implements java.io.Serializable, xxx {
    
       static { i = 4; }
       static { j = 5; }
       static int i,j;
       int ii,jj;
       static int k;
    
       top() {  ii = 5; }
       top(int rj) { jj = rj; }
    
       top(float x) { /* do something */ }
    
       float bar(  float x ){ return bar$$Ctop( x ); } 
       final float bar$$Ctop( float x ) { /* do something more */ } 
       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 (see below):

    class top implements java.io.Serializable, xxx {
       static int i,j;
       int ii,jj;
    
       static { i = 4; }
    
       top() {  ii = 5; }
       top(int rj) { jj = rj; } 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 more */ }
       static int k;
       static { j = 5; }
    
       top(float x) { /* do something */ }
    
       float foobar() { bar$$Ctop(4.0);
                        foo$$Ctop(0, 0); }
    
       public void foo( float x, float y ) { /* something more */ }
    }

    We explain later how methods are composed and where all the additional methods come from.  For now, remember it is possible to compose two class extension files to produce a composite extension file.  As in the case of interfaces, a base file is a "constant" and an extension file is a "function" in GenBorg.  So composing a constant with a function (e.g., f(a)) produces a constant, and composing a pair of functions yields a composite function.

    In the following sections, rules for extending variables, methods, and constructors are reviews.

    Rules for Composing Variables

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

    This begs the question of inadvertent capture.  What if a temporary variable x is defined in the base class and a different variable, also named x, is defined in the extension class?  As mentioned above, PJ will complain.  The clashing of names for temporary variables should be handled automatically.  In a future version of PJ, we plan to add the capability of defining local variables whose names are mangled so that inadvertent capture is not possible.

    Rules for Composing Methods

    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 extension class to call arbitrary methods of their "superclass" or rather, "super extension".  And part of the difficulty arises because PJ is a preprocessor which doesn't really understand type information. 

    You know that a subclass can call method foo() of its superclass by invoking super.foo().  By analogy, a class extension can call method foo() of its super extension by  invoking Base().foo().  Because PJ is a preprocessor that does not type check programs, if an extension calls method of its super extension with signature foo(int, float, String), it does so by invoking Base(int,float,String).foo(5, 3.4, "me").  The token Base is a reserved keyword of PJ.  Base is always followed by the type signature of the method that is to be invoked.  Again, PJ is a preprocessor that doesn't understand type information.  Base is the way the type signature of a "base-class" method is conveyed to PJ.

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

    If a base method is:

      Before Composition After Composition in Composite Class
    Base class
    void foo( ) { 
       /* do something */ 
    }
    final void foo$$baseLayer() { 
       /* do something */ 
    }
    void foo( ) { 
       foo$$baseLayer();
       // something more
    }
    Extension class
    void foo( ) { 
       Base( ).foo();
       // something more
    }
      Before Composition After Composition in Composite Class
    Base class
    void foo( ) { 
       /* do something */ 
    }
     
    void foo( ) { 
       // something more
    }
    Extension class
    void foo( ) { 
       // something else
    }
      Before Composition After Composition in Composite Class
    Base class
    void foo( ) { 
       /* do something */ 
    }
    final void foo$$baseLayer( ) {
       /* do something */
    }
    void foo( ) {
       foo$$baseLayer();
    }
    void bar( ) { 
       foo$$baseLayer();
       // something more
    }
    Extension class
    void bar( ) { 
       Base().foo();
       // something more 
    }

    There is an additional rule for extension methods: any extension method that does not override a base method is added to the composite class.

    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 extended.  This is the position taken in PJ.  New constructors can be added by an extension, but no existing constructor can be refined.  

    Constructors can be extended using the concepts already presented. The body of a constructor is moved into a method, which can be extended.  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):

    Inheritance Hierarchy Extension Hierarchy 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;
       }
    }
    extends foo {
       int b;
       void fooConstructor(int a) {
          Base(int).fooConstructor(a);
          b = 0;
       }
    }

    The New and Overrides Modifiers

    A convenient and optional feature is to designate methods of an extension by the modifiers "new" or "overrides".  "new" means that the extension is defining a new method whose name should not be used by a method in the base interface.  "overrides" means that the extension is defining an extension to a previously defined method. PJ generates an error if it detects that the conditions for "new" or "overrides" are not satisfied.  As an example,

    package mid;
    
    extends class c {
       new void d() { }
       overrides void b() { /* new code */ }
    }

    The above class extension defines a new method (d) and a method (b) that overrides a method in the base class of c.

    The use of "new" and "overrides" makes a useful contribution to avoiding inadvertent capture; these modifiers should be used to guarentee that a method being defined by an extension is in fact a new method or does override an existing method.