Modifying Types

One of the most subtle and important features of StrataCode is the ability for one layer to modify the types in a layer it extends.

Modifying a type is similar to extending a type in a base class, but with a couple key differences. A modify operation only uses a single type name and does not create a new type like extends. Modifications occur at code-generation time - applying to the same runtime class. When you use extends, you create a new name that exists at runtime.

Layers can be used to separate different aspects of the same thing, but using a combined namespace. One great way to use layers is to separate code based on the nature of the code or framework dependencies added by the code. For business applications, define domain and view models that have no dependencies on external frameworks, so they can be reused in any situation. Extend those layers with UI and database layers that enhance the same types injecting framework dependecies downstream. With this pattern, you reduce the copies of that logic and ensure that all dependencies are traceable with find usages, replace etc.

Just like with 'extends' though, when you modify a type you can override property values and methods, and use the super operator to refer to the previous implementation. You can add new fields, new methods or property assignments. A layer can replace the class entirely in a previous layer, or it can replace a field definition, changing it's type and initializer, or it can just replace the initializer. It can modify the extends or implements statements of the superclass.

It is only safe to redefine a class where the new class has a superset of the contract from the base layer - i.e. fields and methods which match the parent methods. Layers which preserve the contract maintain compatibility with subsequent layers and are much easier to maintain.

The syntax for modifying a type is to add a file with the same type name (package plus class) in a layer which extends that layer. Make sure the full package name and file name of your file match that of the downstream layer or you get an error. Remember the full type name of any file in StrataCode is the combination of the layer's package prefix along with the file name.

Here are a few examples. If in the base layer you define this class:

public class Foo {
   int x;
}
In the sub layer you can modify the class with:
Foo {
   x = 3;
}
The generated Java class will be:
public class Foo {
  int x = 3;
}
You can also override methods just as if you were extending from the Foo class.
public class Foo {
   public void hello() {
     System.out.println("hello");
   }
}
In the sub layer you could do:
Foo {
   public void hello() {
     super.hello();
     System.out.println("goodbye!");
   }
}
The StrataCode compiler generates one class named Foo. The replaced method is renamed _super_layerName_typeName_methodName and all super.method() calls are remapped to the new name. You can add a new field in one layer:
Foo {
  int newField;
}
and you can replace the field definition with:
Foo {
   public int newField;
}
You can't make incompatible changes in a subsequent layer. For example, this is a compile error:
Foo {
  // Error - can't change the type of a field
  public float newField;
}
A layer can use the override keyword to just change attributes without specifying the type again:
// Adds the final attribute to newField
Foo {
  override final newField;
}
You can also define new sub types and modify existing sub types:
Foo {
   class Bar {  // add's an inner class
     int barProp;
   }
   Baz {  // modify an existing inner class named Baz and override a property 
     bazProp = 3;
   }
}
You can also add or modify object instances:
Foo {
   object bar {  // add's an inner instance of a singleton type named bar
     int barProp;
   }
   baz {  // modify an existing inner object named baz defined in Foo
     bazProp = 3;
   }
}
Or replace the extends class with a compatible type:
Foo extends Fum {  // changes Foo's base class to Fum.
}
Foo either should not have a base class or its base class should be a subtype of Fum to enforce the "compatibility constraint" with layers.

You can use a modifier to modify an inner type which is inherited by the parent type. For example, if I have a base class B, subclass C which extends B, if B defines an inner object, I can modify that in C:

class B {
   object childOfB {  
     int barProp;
   }
}
// OK
class C extends B {
   childOfB {  
     barProp = 3;
   }
}
Note however that in this case, the childOfB modify operator creates a new type in the system. Effectively StrataCode converts this to:
class C extends B {
   object childOfB extends B.childOfB {  
     barProp = 3;
   }
}
This is a subtle but important case to support. It lets you modify the definition of any property underneath your given type, creating new types transparently as needed so that customization is only applied to the right instances.