Visage Reflection (The Visage Language in Depth) (JavaFX 2)

Visage includes a reflection API that allows you to perform certain metaprogramming tasks. Metaprogramming is the act of manipulating a programming facility using a language rather than using the programming facility itself.

The Visage provides its reflection API as a set of Java classes in the Visage runtime library. The fact that the Visage reflection API is written in Java rather than in Visage means that you can use it in either Visage code or Java code. We will show you its usage mostly in Visage.

Because the reflection API is written in Java, we will use Java’s terminology to describe its parts. Therefore, in the upcoming text we will talk about fields and methods instead of instance functions and instance variables.

Mirror-Based Reflection

Visage’s reflection facility follows a design principle called mirrors, which seek to separate conventional programming facilities from metaprogramming facilities and at the same time achieve symmetry between the conventional and metaprogramming facilities.

■ Note The Java reflection facility does not follow the mirrors principle. This is mostly manifested through code like employee.getClass(), where employee is an object of an Employee class. Although methods like getEmployeeId() or getName() have a legitimate place in an Employee class, one can argue that getClass() does not.


The interfaces and abstract classes in the visage.reflect package presented in Table A-2 provide the fundamental abstractions of the reflection facilities of the Visage programming language.

Table A-2. Interfaces and Abstract Classes of the Visage Reflection API

Name

Visage Concept

VisageType

A Visage type

VisageValue

A Visage value

VisageMember

A member of a class or a module

VisageLocation

The location of a variable, used in data binding

VisageContext

A reflection context, the entryway into reflection

Concrete subclasses of the VisageType abstract class include VisagePrimitiveType, VisageSequenceType, VisageFunctionType, VisageClassType, and VisageJavaArrayType. This reflects the four kinds of Visage types plus the native array types that we covered earlier.

Implementations of the VisageValue interface include VisagePrimitiveValue, VisageSequenceValue, VisageFunctionValue, and VisageObjectValue. VisagePrimitiveValue has additional subclasses: VisageBooleanValue, VisageIntegerValue, VisageLongValue, VisageFloatValue, and VisageDoubleValue.

Implementations of the VisageMember interface include VisageFunctionMember and VisageVarMember. They reflect instance functions and instance variables as well as script functions and script variables. Script functions and script variables are considered members of the script or module to which they belong.

Concrete subclasses of the VisageLocation abstract class include VisageVarMemberLocation.

Entering the Reflection World

By design, the Visage reflection API can support reflections in a local Java virtual machine as well as remote Java virtual machines. Visage ships with a concrete implementation for local Java virtual machine reflections called VisageLocal. Its getContext() method returns a VisageLocal.Context object, which is an implementation of VisageContext.

The VisageLocal.Context class has a family of overloaded mirrorOf() methods that bridges the conventional programming and metaprogramming world. To get a VisagePrimitiveValue or VisageObjectValue value from a primitive value or an object, you use a version of mirrorOf() that takes one parameter. To get a VisageSequenceValue or VisageFunctionValue value from a sequence or a function, you use a version of mirrorOf() that takes two parameters, the second parameter being the type of the sequence or the function.

The VisageValue objects are where further reflection tasks are performed. Its getType() method returns an appropriate VisageType subclass. Its asObject() method returns the original Visage value.

Listing A-72 shows an example of obtaining and examining the mirror reflections of the four kinds of Visage values.

Listing A-72. Examining the Mirrors of Conventional Visage Types

Examining the Mirrors of Conventional Visage Types

 

 

 

 

 

Examining the Mirrors of Conventional Visage Types

Notice that, to obtain the mirrors of the sequence and function values, we have to construct their types beforehand and pass them into the mirrorOf() method as the second parameter. To obtain the type of a Number sequence, we call getNumberType() on context and then getSequenceType() on the result. The VisageLocal.Context class has methods that return the VisagePrimitiveType object of every primitive type. To obtain the function type, we call findClass() on context to get the VisageClassType, getFunction() on classType to get the VisageFunctionMember, and finally getType() on funcMember to get the VisageFunctionType.

When we run the program in Listing A-72, the following output is printed to the console:

tmp47-401

 

 

 

 

 

tmp47-402

Notice that the type of the Number sequence is reported as Float[], which is the backing class of the Visage Number type.

Programming Through Reflection

In this section, we show you a series of small code snippets that can be used to perform some common programming tasks using reflection. Reflection code is always considerably longer than nonreflection code. However, its length depends on the classes and functions being programmed. The idiomatic way of creating a new instance of a Visage class is as follows:

tmp47-403

Here, classType is of type VisageLocal.ClassType, a subclass of VisageClassType, and objectValue is of type VisageLocal.ObjectValue, a subclass of VisageObjectValue. The allocate() call allocates the memory for an instance of the class. The initVar() call supplies the initial values of instance variables of the object. Another version of initVar() takes an VisageVarMember object as the first parameter instead of a string. The initialize() call performs the actual setting of the instance variables to their initial values and the running of the init and postinit blocks that the class may have.

Getting and Setting Values of Instance and Script Variables

The following code works with instance variables of Visage classes and script variables of Visage modules:

tmp47-404

Here, we obtain the mirror of an instance variable in a class using the getVariable() call on classType, passing in the variable name. Two other methods exist for getting instance variables. One version of the overloaded getVariables() call takes a Boolean parameter and returns a java.util.List<VisageVarMember>. If the parameter is false, only instance variables declared in the class are included in the list. If the parameter is true, all instance variables, including those declared in the superclasses, are included. Another version of the getVariables() call takes an extra first parameter of type VisageMemberFilter, which allows you to get only those instance variables that pass the filter.

The type of xVar is VisageVarMember. It represents the instance variable x of class Point. The setValue() call on xVar takes two parameters: the first is a mirror of p and the second is a mirror of the new value of x. VisageVarMember has a corresponding getValue() call, which takes one parameter, the mirror of an instance of the class, and returns a mirror of the value of the instance variable.

■ Note The Visage compiler compiles Visage modules into Java classes and Visage classes in a module into nested Java classes. Therefore, the reflection facility represents both classes and modules as VisageLocal.ClassType. The getVariable() and getVariables() calls on a module class type will return information related to script variables in the module. The setValue() and getValue() calls on instances of VisageVarMember that represent script variables will ignore the first parameter, and we usually pass in null.

Invoking Instance and Script Functions

The reflection code for invoking instance functions and script functions is very similar to the code for accessing instance variables and script variables, as shown here:

tmp47-405

Here, we call the getFunction() method on classType to obtain a mirror of an instance function. This method can take a variable number of parameters. The first parameter is the name of the function. The rest of the parameters are mirrors of the types of the instance function. Two other methods are available for getting instance functions. One version of the overloaded getFunctions() call takes a Boolean parameter and returns a java.util.List<VisageVarFunction>. If the parameter is false, only instance functions defined in the class are included in the list. If the parameter is true, all instance functions, including those defined in the superclasses, are included. Another version of a getFunctions() call takes an extra first parameter of type VisageMemberFilter, which allows you to get only those instance functions that pass the filter.

The type of fFunc in the preceding code is VisageFunctionMember. It represents the instance function f of class A. The invoke() call on fFunc takes a variable number of parameters, in this case two: the first parameter is a mirror of o, an instance of A, and the second parameter is the mirror of the instance function parameter. It returns an instance fVal of type VisageValue that represents a mirror of the value of the function invocation expression.

The situation with the script function g is analogous to the situation for instance functions, except that we search for the script function in the module class type, and that we pass a null as the first parameter to the invoke() call.

Other Reflection Capabilities

The reflection API also provides the following capabilities:

• The VisageLocal.ClassType class has getMember(String, VisageType), getMembers(boolean), and getMembers(VisageMemberFilter, boolean) methods to get at member functions and member variables through the same API.

• The VisageLocal.ClassType class has a getSuperClasses(boolean) method to get a list of direct or indirect superclasses.

• The VisageLocal.ObjectValue class has overloaded bindVar(String, VisageLocation) and bindVar(VisageVarMember, VisageLocation) methods to bind member variables to VisageLocations associated to other VisageVarMembers.

• The VisageVarMember class has a getLocation(VisageObjectvalue) method that gets the location of member variables for use in bindVar() calls.

Resources

The best place for more information about the Visage language is the official Visage compiler home page at http://visage-lang.org.

Next post:

Previous post: