ComponentJ¶
ComponentJ is a programming language for the Java platform which favors code reuse by means of composition instead of implementation inheritance. Components and objects are the main ingredients of a ComponentJ program, very much like classes and objects in Java. A chief difference is that components are first-class values in ComponentJ (or runtime values), in opposition to classes in Java, which are compile-time values.
ComponentJ is based on a component core calculus first introduced in the paper “A Basic Model of Typed Components” and then extended to cope with type safe dynamic component reconfiguration in "Types for Dynamic Reconfiguration". Both papers formally present the basic concepts of the language and develop a type system that ensures the safety of the language operations. Hence, componentJ programs do not have runtime errors due to the assembly of components and objects.
ComponentJ integrates well with Java. The compiler takes ComponentJ type declarations and produces two kinds of output code that help integrate ComponentJ and Java code. From a component type one can produce skeleton classes that allow one to natively program a component. On the other hand, one can produce stub classes that allow Java programs to use ComponentJ components and objects.
Publications¶
Margarida Piriquito. Type System for the ComponentJ Programming Language. Master Thesis. 2009. PDF
João Costa Seco, Ricardo Silva, Margarida Piriquito. ComponentJ: A Component-Based Programming Language with Dynamic Reconfiguration. Computer Science and Information Systems. Vol 05. Number 02. 2008. PDF
João Costa Seco, Ricardo Silva, Margarida Piriquito. ComponentJ: component programming and reconfiguration. In Proceedings of CoRTA (Compilers, Related Technologies and Applications) - July, 2008.
João Costa Seco. Languages and Types for Component-Based Programming PhD Thesis, 2006. PDF
João Costa Seco and Luís Caires. Types for Dynamic Reconfiguration. In Proceedings of the European Symposium on Programming (ESOP), 2006. PDF
João Costa Seco and Luís Caires. Subtyping First-Class Polymorphic Components. In Proceedings of the European Symposium on Programming (ESOP), 2005. PDF
João Costa Seco and Luís Caires. ComponentJ: The Reference Manual Technical Report UNL-DI-6-2002. Monte da Caparica, Portugal, 2002, [PDF].
João Costa Seco and Luís Caires. A basic model of typed components. In ECOOP’2000 14th European Conference on Object-Oriented Programming, 2000. PS
- ComponentJ
- Publications
- ComponentJ Main Concepts
- The Language
- Configurators
- Producing a Component
- Instantiating Components
- Reconfiguration
- Imperative Features
- Type System
- Getting the compiler
- Usage
- Advanced usage
- Skeletons
ComponentJ Main Concepts¶
The ComponentJ language follows four base principles:
- making all dependencies between modules explicit.
- systems should be able to be dynamically constructed.
- support of the modification of the behavior of objects in runtime.
- the presence of a type system to ensure good properties on the code.
In order to pursue these principles we developed a component core model, which we now explain:
Objects, like in any other object oriented language, aggregate state and functionality. Functionality is described through interfaces, like the ones present in the Java language, that specify which methods an object must implement. However, in our language, unlike what happens in Java, multiple interfaces aren't flattened in a single namespace, but are instead accessed each through it's own named port.
To provide its functionality, through one or several ports, an object must declare which ports (and thus interfaces/services) it provides. It may also require the use of some functionality provided by other objects and thus must also declare it. An object can have two sets of ports, one of provided ports (which indicate the services available from this object) and one of required ports (which indicate what services are needed by the object). Required ports must be provided at instantiation time and are only visible inside the object structure.
Apart from the two sets of ports already described, the internal structure of an object can be composed of two more entities: other objects and method blocks. Method blocks are composed of state variables and methods and can be used to directly provide or adapt a service. Other objects can be used in such adaptations or simply to directly provide some service. The internal elements here described are not, in any way, visible to outside use, thus ensuring the encapsulation. Objects, once instantiated and all required dependencies solved, are distinguishable only by their set of provided ports.
A Component defines how objects are organized, much like a class in class-based languages. In order to define a component one can declare all the constituents of an object (provided and required ports, internal objects and method blocks) and additional plug operations. A plug operation defines a link between two entities where the source can be a port (required or provided by an internal component) or a method block and the destination can be a provided port (from the object the component defines) or a required port (from an internal object). These are all called configurators because they define the configuration of the objects produced by this component.
Components and even configurators are first class values in our language and can thus be manipulated, constructed and instantiated at runtime.
The Language¶
ComponentJ programs consist of a series of declarations and an optional block of code. The block of code will become the executable produced by that ComponentJ program, however this is not the preferred way to use ComponentJ.
ComponentJ should be used as a language to define how components interact with each other, rather than to define a complete program or system. For that reason the imperative statements it supports are quite limited (but computationally complete), and so is the support for building standalone applications (for example, there is no way to access command line arguments).
The declarations that may be present in a ComponentJ program are the following:
- component
- port interface
- component interface
- object interface
A port interface is much like an interface declaration on Java:
port interface ICounter {
int tick(int amount);
}
declares an interface, named ICounter, that has a method tick.
A component interface on the other hand, defines the ports provided and required by the components that respect it:
component interface TCounterChain {
provides ICounter p;
requires ICounter r;
}
declares a component interface, named TCounterChain, that provides a port p and requires a port r, both of type ICounter.
A object interface follows the same principle but applied to objects, for which all requirements are already established:
object interface OCounter {
provides ICounter p;
}
Besides being able to use user defined types (components or port interfaces), when programming in ComponentJ one also has a series of basic types:
- int
- float
- double
- boolean
- char
- string
- port
- object
- component
- script
The first few ones are what one would expect from any typed language: numeric and string types. The last ones (port, object, component, script) equate to types from our component model (ports, objects, components and configurators).
This is very likely to change in a future version of ComponentJ.
The type system is bound to carry more information than the one carried by the above types. There should be component types, object types, and script types allowing for a more detailed verification of soundness properties.
Components are declared as follows:
component Counter {
provides ICounter p;
methods m {
int s = 0;
int tick(int amount) {
s = s + amount;
return amount;
}
};
plug m into p;
}
A component declaration is comprised of the keyword component, a name, and a sequence of configurators. Configurators will be discussed in detail in the next subsection.
Configurators¶
Configurators are the building blocks of components, and define how objects are produced (and later on, changed).
There are five kinds of configurators (one for each of the four ingredients for objects and another one for linking):
Provides¶
Declares a provided port. Must specify name and type (interface) of the port.
provides ICounter p;
Requires¶
Declares a required port. Must specify name and type (interface) of the port.
requires ICounter p;
Uses¶
Declares an internal object. Must specify name for the object and an expression that generates a component.
uses c = Counter;
Methods¶
Declares a method block. Must specify a name for the method block and its implementation.
The method block can have state variables and method declarations. All state variables are private (not visible outside the method block) and all methods are public (potentially visible outside the method block).
methods m {
int s = 0;
int tick(int amount) {
s = s + amount;
return s;
}
};
This is very likely to change in a future version of ComponentJ.
Plug¶
Declares a link between two resources. The first name is called the source while the second is called the destination.
The names can be simple (like in the example) or compound (two simple names separated by a dot).
Names have different semantics depending on whether they are source or destination. A simple source name can refer either a required port or a method block, while a simple destination name can only refer to a provided port. The first part of compound names always refer to internal objects while the second part of compound names have different meanings depending on where they appear in the plug configurator -- a required port if it appears as a destination, and a provided port if it appears as source.
plug m into p;
Producing a Component¶
Besides declaring a top level component, one can also dynamically create one. For that the programmer must assemble the configurators (using the sequential composition operator ';'), and use the compose operation on such a script in order to produce a component:
component counter = compose(
provides ICounter p;
methods x {
int s = 0;
int tick(int amount) {
s = s + amount;
return amount;
}
};
plug x into p);
Please note that all configurators are expressions, and can as such be replaced by referencing some variable that contains them, or even by calling some method that produces them.
script buildCounter = (
provides ICounter p;
methods x {
int s = 0;
int tick(int amount) {
s = s + amount;
return amount;
}
};
plug x into c
);
component counter = compose(buildCounter);
Instantiating Components¶
To instantiate a component the new keyword can be used.
This is very likely to change in a future version of ComponentJ.
OCounter o = new Counter;
With OCounter being an object interface.
Whose ports can be accessed to call methods
o.p.tick(5)
Reconfiguration¶
Reconfiguration is achieved through the reconfig statement, which is an imperative statement and can thus be used in method definitions and in the main program.
reconfig o using s with [ c := o.p ] as b in {
//things to do if successful
} else {
//things to do in case of error
}
the statement takes an object and applies a script to it. If the reconfiguration is successful the statements in the in branch are run, if its not, the statements in the else branch are run. Both branches must be specified (even if with null statements). The with clause is required when new required ports are introduced, and forbidden if no required port is introduced by the script. There needs to be a specification of a new name for which the component will be known in the in branch.
As an example:
script haltCounter = (
methods m {
int tick(int amount) { return 0; }
};
plug m into p
);
OCounter o = new Counter;
reconfig o using haltCounter as h in {
h.p.tick(10); // returns 0;
}
else {
o.p.tick(10); // returns 10;
}
defines a script named haltCounter, and tries to reconfigure the newly created object o with it.
Imperative Features¶
The imperative features of the ComponentJ language are usable when declaring methods in method blocks, or on the block of code that denotes ComponentJ's main program.
In ComponentJ one can use a rather extensive set of imperative features, including variable declaration, arrays, indexing, control structures. All these, except array definition, have the same syntax as a Java program.
The control structures available to the ComponentJ programmer are limited to the if and while statements.
Array indexing is done as in Java, but the creation of a new array is done with the newarray keyword instead of the new keyword (which is already used to instantiate objects).
int[] a = newarray int[20];
int i = 0;
while (i < 20) {
a[i] = -1;
i++;
}
Type System¶
TODO
Getting the compiler¶
Link here
Usage¶
Unix¶
After setting the environment variable CJ_HOME is set to the directory containing componentj.jar and componentj_runtime.jar, it is possible to use the provided shell script cjc to directly compile from .cj to .class files. It will create a directory named tmp in the current working directory, to place the generated .class and .java files.
format: ./cjc [-skels] FILE1.cj FILE2.cj ...
The generated code is placed in a directory tmp under the current directory, and it can be ran using the following command
java -cp componentj_runtime.jar:tmp CJMain
Windows¶
After setting the environment variable CJ_HOME is set to the directory containing componentj.jar and componentj_runtime.jar, it is possible to use the provided cjc.bat to directly compile from .cj to .class files. It will create a directory named tmp in the current working directory, to place the generated .class and .java files.
format: cjc [-skels] FILE1.cj FILE2.cj ...
The generated code is placed in a directory tmp under the current directory, and it can be ran using the following command
java -cp componentj_runtime.jar;tmp CJMain
Manually¶
Alternatively, it can be done step by step.
To compile from .cj to .java, we must use the ComponentJ compiler:
java -cp componentj.jar main.CJCompiler [-d OUTPUT_DIR] [-skels] FILE1.cj FILE2.cj ...
If the .java files were generated with the option -d tmp, it is possible to compile and run the default main (if defined) using:
javac -cp componentj_runtime.jar tmp\*.java
java -cp componentj_runtime.jar;tmp CJMain
Advanced usage¶
The ComponentJ compiler features two mechanisms that help integration with the Java Language. Stubs are used to wrap ComponentJ components (and objects produced by them) in a way that allows easy and direct access to the implementation of its instances' ports. Skeletons are a way to guide component programmers through the process of building a component usable in ComponentJ programs using the Java language.
Stubs¶
To use a stub created by a component interface we must do two things:
- narrow the ComponentJ component to that interface.
- Use the createInstance method(s) to create an instance of the object.
Narrowing is done using the ''narrow(Component)'' method of the stub created with the name of the component interface. The narrowed component's instances will have type ComponentInterface.InstanceType, and will have a ''get_X()'' and ''port_X()'' methods for each provided port declared in the interface. The ''get_X'' method returns the implementation for the service and its return value can be directly used to call upon the service's methods; the ''port_X'' method returns the actual port provider which can be mainly used to connect to required ports from other objects. In order to instantiate an object from a component with required ports after using the ''createInstance'' method, we must use the ''withPort(String, PortProvider)'' or ''withObject(String, CJPort)'' methods to connect implementations to the required ports BEFORE accessing other ports.
Example¶
TPrint tp = TPrint.narrow(SystemPrint.asValue());
//We narrow an existing component to get a component type
TPrint.InstanceType printer = tp.createInstance();
//We can create instances using the createInstance() method
//We need to explicitly invoke instantiation's second stage
printer.stretch();
printer.get_p().print("Hello World (from Java)!");
//We cleanly access the instance's services.
If we have a "Hole" component we will need to provide it with an implementation for the service. We could define its type using
component interface THole {
provides IPrint p;
requires IPrint r;
}
And use it in the following manner:
THole thole = TPrint.narrow(PrintHole.asValue());
THole.InstanceType hole_inst = thole.createInstance();
hole_inst.withPort("r", printer.port_p());
// Second stage only invoked after ports are connected
hole_inst.stretch();
hole_inst.get_p().print("Hello World (from Java through a Hole)!");
Skeletons¶
Skeletons are a feature best shown by example, so we will implement the Java native SystemPrint component using skeletons.
From the TPrint type
component interface TPrint {
provides IPrint p;
}
we get the ''TPrintBaseSkeleton'' class which we can subclass to implement our ''SystemPrint'' component.
import library.*;
public class SystemPrint extends TPrintBaseSkeleton {
// in order to incorporate this component into the
// ComponentJ framework it needs to provide a
// singleton factory method, asValue which is
// implemented in this manner
private static Component _val = new SystemPrint().component();
public static Component asValue() {
return _val;
}
// The TPrintBaseSkeleton class is abstract and requires
// the implementation of the getInstance method, which
// returns an instance capable of implementing the required
// services.
//
// We will define a static internal class named Instance which
// inherits from the InstanceSkeleton class defined in TPrintBaseSkeleton
public CJObject getInstance() {
return new Instance();
}
// In this example the instance implements the service
// directly, so it must implement the interface.
private static class Instance extends InstanceSkeleton
implements IPrint {
// for each declared provided port the instance
// is required to supply an implementation for it.
protected IPrint get_p_provider() {
return this;
}
// The following methods define the implementation of the
// IPrint service
public void print(String s) {
System.out.println(s);
}
public void printInt(int i) {
System.out.println(i);
}
}
}