Each Bistro metaclass is implemented as a nested public static class. The metaclass name is always mClass
. Each Bistro class has a public static member ($class
) that refers to the sole instance of its singleton metaclass. However, because metaclasses are singletons, they do not support the definition of abstract methods. Instead, metaclasses must provide default implementations for methods that would otherwise be declared abstract.
Figure 1 shows the full inheritance hierarchy for some of the essential behavior classes. Each class has a static link to its metaclass ($class
). However, these links must be resolved (by instantiation) after the inheritance links have been established (during compilation). Making each metaclass a public static class enables this. Finally, the metaclass ($class
) links of all the metaclasses refer a singleton public static instance of the class Metaclass.
Figure 1: Classes and metaclasses.
All root classes, those derived from nil
, have inheritance and metaclass structures like that of smalltalk.behavior.Object
. A Bistro class can also be derived from a Java class. In this case, the inheritance and metaclass structures of the generated classes also look like that of smalltalk.behavior.Object
; that is, they serve as the root of an inheritance hierarchy derived from a Java base class. Finally, a Bistro class derived from a Java class may also be declared without a metaclass. In this case, the generated Java class will simply be another Java class, without a generated metaclass. Listing Five (HelloWorld) provides an example of a simple console application that has no metaclass defined.
Listing Five
"HelloWorld.bist" package: smalltalk.example; import: smalltalk.stream.Transcript; "Demonstrates Bistro syntax and Java integration." Object subclass: HelloWorld class: [ "Supports program launch from the console." static (void) main: args (java.lang.String[]) [ HelloWorld basicNew printHello. ] "Prints hello on the console." printHello [ Transcript printLine: 'Hello World!'. ] ]
Types and Metatypes
Support for object interfaces is one of the more powerful and innovative features of Java. Interfaces provide a language mechanism for defining polymorphic behavior independent of inheritance. Bistro supports the definition and use of interfaces as types. However, because Smalltalk supports first-class metaclasses and full metaprogramming, Bistro takes a further step by coupling each type with a metatype. This gives you the ability to coordinate programming and metaprogramming.
Each Bistro type and metatype is translated into a Java interface definition. Each metatype is defined as a nested public static interface of its corresponding type, similar to the relationship between a class and its metaclass. As with Java interfaces, Bistro supports type inheritance. As with classes and metaclasses, the metatype inheritance structures parallel the type inheritance structures. When a Bistro class implements a type, the metaclass also implements the metatype. But, when a Bistro class implements a Java interface, no corresponding metatype exists. In this case, the generated metaclass does not implement a metatype.
Access Controls and Decorations
Access controls play an important role in defining contracts in object-oriented designs. Like Java, Bistro provides access controls on classes, types (interfaces), methods, and variables. Bistro supports the Java reserved words that specify access control including public, protected, or private. While each class, method, and variable may be declared with one of these access controls, Bistro uses the common Smalltalk conventions for default access when no explicit control has been supplied. By default, Bistro classes and types are public, methods are public, and variables are protected. Also, Bistro metaclasses and metatypes are always public. All access controls are enforced at runtime by the Java VM.
Like Java, Bistro supports the use of several reserved words in variable and method signatures, including those that control subclass derivation and thread synchronization. An abstract method must be implemented in the subclasses of the class in which it is defined. A final method cannot be implemented (overridden) in subclasses. A native method must be implemented using a Java Native Interface (JNI). A static method is not polymorphic like the methods defined in a metaclass. However, a static method can be used to define the main entry point for a console application. A synchronized method excludes concurrent usage by different threads.
Conclusion
In December of 2000, the Bistro project reached completion of its initial goals. In April of 2002, with the advent of support for identity collections in J2SE 1.4, the Bistro class library was improved to support IdentityDictionary
and IdentitySet
from the ANSI Smalltalk Standard. The essential portions of the SUnit library have been ported to Bistro. Listing Six is one of the smaller classes from that library. The next phases of the Bistro project include: adding support for pools, adding more unit tests (especially ANSI conformance tests), and improving conversions between Smalltalk and Bistro.
Listing Six
"TestSuite.bist" package: smalltalk.test; import: smalltalk.collection.OrderedCollection; "A TestSuite is a Composite of test cases. It runs a collection of test cases." Object subclass: TestSuite metaclass: [ "Returns a new TestSuite." new [ ^super new initialize ] ] class: [ "accessing tests" "Contains the tests for the suite." tests (OrderedCollection). "Returns the test cases in the suite." tests [ ^tests ] "Establishes the test cases of the suite." tests: anOrderedCollection [ tests := anOrderedCollection. ] "Adds (aTestCase) to the suite." addTest: aTestCase [ self tests add: aTestCase. ] "initializing" "Initializes the receiver." initialize [ self tests: OrderedCollection new. ] "running tests" "Runs the test cases in the suite." run [ result := TestResult new. self run: result. ^result ] "Runs the test cases in the suite, recording their results in (aTestResult)." run: aTestResult [ self tests do: [ :each | each run: aTestResult ]. ] ]
At a minimum, Bistro requires the capabilities of the Java 2 Standard Edition (J2SE), especially anonymous inner classes (which are used for blocks), reflection (which is used for dynamic methods resolution and dispatch), and the java.util and java.io packages (which are used to implement the standard Collections
and Streams
).
Nik has been designing and developing object-oriented software systems since 1987. He can be contacted at [email protected].
Bistro.zip is a file containing the source code that accompanies this article.