The programming philosophy of JAC is to let the functional (core-business) program components free from any reference towards all the technical or business extensions that it needs to be effective. Thus, JAC functional components do not have to implement special interface or extend special classes. All this is done seamlessly by the system.
Thus, it can be troubleshooting for the programmer when s/he makes its first JAC program. The idea is to concentrate on the core functionalities and to make a program as if you were a very beginner in Java. For instance, even if you know that you will need to display a given component or that a given component will need to be persistent, just program a regular Java class and do not try to import any packages that deal with persistence or GUI, all these will be done in a second step and will not require any reengineering.
This magic stuff is possible because of the aspect-oriented framework implemented by JAC. This framework builds a set of classes and objects that allows the programmer to define new aspects or use existing ones in a very simple fashion. Once configured, the aspects will be able to deal with your components the way you expect and introduce new concerns on them (e.g. persistence, transaction, distribution, fault-tolerance, ...).
However, since existing aspects are generic and may be applied to several programs, they have made some assumptions on how your program works and expect the programmer to follow some rules that can be seen as restrictions. Some programming restrictions example are given below (note that most of these restrictions will be overcome in further versions).
jac.lib
package. Thus, if you need the
persistence to wrap collections (however it is not
mandatory), use, for instance,
jac.lib.java.util.Vector
instead of
java.util.Vector
. For examples on how to use
wrapped collections, see the photos sample.In most cases, a regular programmer will find the aspects s/he needs for its application provided by the JAC distribution (well, this will be the case in a close future, we hope). As a consequence, programming an aspect-oriented application is just a matter of choosing the right aspects and configure them so that they behave properly for the final program.
Each existing aspect within the distribution corresponds to an aspect component (see the JAC architecture overview for more details). Each aspect component is documented so that any final programmer must be able to use it just by taking a look at its API (in the worst cases, some code reading may be necessary). Here is a list of the most useful aspect components provided by JAC (with a direct link towards their API documentation).
NOTE: a useful piece of documentation is also the package that corresponds to a given aspect component. In general, we explain here how it works and make a short summary of its configuration methods (for instance, see persistence, or GUI).
There are two manners to customize an aspect for your own application needs.
You write a configuration file for your aspect and use it to
call all the parametrisation methods that you need. These
configuration files are usually in the same directory than the
application *.java
files and are *.acc
files (for aspect-component configuration).
Here is a sample configuration file for the persistence aspect
of the photos sample (see persistence.acc
in the
sample directory).
configureStorage "jac.aspects.persistence.PostgresStorage" \ { "photo", "jac" "" }; configureClass "Photo" persistent; configureClass "PhotoRepository" root; configureClass "Person" root; wrapCollection "PhotoRepository" photos; registerStatic "photorepository0";
Note that all the called methods
(configureStorage
, wrapCollection
, and
registerStatic
) are part of the API of the
PersistenceAC
aspect component.
Once you have written this file, the advantage is that JAC will be able to parse it dynamically and instantiate the corresponding aspect component. This is a very flexible mechanism since the aspect can be unwoven and woven again with a different configuration while the application is running. The following technique is more static.
However, in some cases, the aspect component will not provide
the configuration API that you need (our aspect components are
more-or-less finalized). In these cases, the programmer needs to
add some configuration methods to the aspect component s/he
needs to use or to create a new aspect component that extends
the existing one so that the configuration is automatically done
at the aspect component's construction time (in this case, you
MUST declare your new aspect within the jac.prop
file).
Important note: in the case you decide to add extra configuration methods to our existing components and if you think they can be useful for others, then it would be great if you could send us your code so that we include them into the next releases. Thanks.
Once all the aspect configurations are made, you must declare to the system that your program uses them. This is done by programming the application's launcher/descriptor.
The program should be launched by a Run
class that
implements a main
method. This launching method is
declared in a JAC application descriptor (*.jac files) that also
registers the program within the applications repository,
construct the supported aspects configurations.
public class Run { public static main( String[] args ) { // create the root object new MyRootClass(); } }
// the application descriptor (myApp.jac) applicationName: myApp launchingClass: myAppPath.Run aspects: \ persistence myPath/persistence.acc true
By default, all the objects are wrappable except wrappers,
aspect components and exceptions. If you need an object to be
regular (not wrappable), you need to declare it in the
jac.prop
file in your JAC distribution root
directory.
To run the application then use the jac
command
that must have been set into you path by
scripts/init_env
.
jac -W -G myAppPath/myApp.jac arg1, ...
To know about the launching options, use the command jac
-h
or refer the JAC launching class jac.core.Jac.
JAC is developed under Linux but will work under all the other platforms that support Java 2. If you run into some installation problems, please, do not hesitate to ask for some help to Renaud Pawlak or Lionel Seinturier
You must keep in mind that JAC is not a language but a framework (see the architecture overview). Thus, most of the programming is done by extending the classes or implementing the interfaces that are provided by the jac.core package.
The next sections present all the programming concepts that are needed to program new aspects from scratch and finally show a simple aspect example.
A wrapping method is a regular method that is implemented within a subclass of jac.core.Wrapper (that implements the jac.core.CollaborationParticipant interface).
public class MyWrapper extends Wrapper { public Object myWrappingMethod () {...}; }
Within the runtime context of a wrapping method, the programmer can access several informations about the current method call (actually the current collaboration's interaction).
A wrapping method can wrap several base object methods. The arguments are seamlessly set to the values corresponding to a given base-level call.
Within a wrapping method, the wrapper can perform treatments and manipulate its arguments. A wrapper task is partitioned in two parts: the before part, that is called before the base method is called, and the after part, that is called after it. Between the before and after parts, the wrapper programmer must call the base object. Since many wrappers can wrap a base object, the call process to the base object is a special process that continues the wrapping chain if needed (and calls the base object if there is no wrappers left). To perform it, the wrapper programmer must use the proceed method provided by the Wrapper class. A wrapper must return an object that stands for the return value of the base function (it has to be a compatible type).
Thus, a typical body of a wrapping method is:
Object ret = null; // before job ... ret = proceed(); // after job ... return ret;
To show how wrappers can be used, here are ten simple examples of very classical wrapping function bodies. Note that a wrapping method can access the wrapper state that is not represented in the following examples.
Example 1:
A verbose wrapping method (that prints a trace on the screen ---
easily modifiable to print in a log file).
Object ret = null; System.out.println("<< calling "+method()+" with "+args()+" >>"); ret = proceed(); System.out.println("<< "+method()+" returned "+ret+" >>"); return ret;
Example 2:
A wrapping method that checks the type of the client and raises an
exception if the client is not allowed (note, this
exception should be handled by an exception handler).
Object ret = null; if ( attr("cient") instanceof AllowedType ) { ret = proceed(); } else { throw new NotAllowedTypeException(); } return ret;
Example 3:
A wrapping method that performs some precondition test on the
arguments (here, the first argument is an integer and
must be bounded within 1-100).
Object ret = null; if (((Integer)arg(0)).intValue() >= 0 && ((Integer)arg(0)).intValue() <= 100) { ret = proceed(); } else { throw new ArgOutOfBoundsException(); } return ret;
Example 4:
Almost the same wrapping method but, that corrects the argument value
instead of throwing an exception.
if (((Integer)arg(0)).intValue() < 0) args()[0] = 0; if (((Integer)arg(0)).intValue() > 100) args()[0] = 100; return proceed();
Example 5:
A wrapper that stores the object state
within a database (to make it persistent).
Object ret = null; ret = proceed(); Database d = new Database(); d.connect(); d.write( wrappee(), Utils.getObjectState( wrappee() ) ); return ret;
Example 6:
A wrapping method that performs a
postcondition test on the return value (in this case, the return
value must be an integer bounded within 0 and 100).
Object ret = null; ret = proceed(); if (((Integer)ret).intValue() < 0 || ((Integer)ret).intValue() > 100) { throw new RetOutOfBoundsException() } return ret;
Example 7:
A wrapping method that performs a test on the state of the
wrappee after the base function has been called.
Object ret = null; ret = proceed(); if (((Integer)wrappee().getFieldValue("aField")).intValue() < 0 || ((Integer)wrappee().getFieldValue("aField")).intValue() > 100) { throw new FieldOutOfBoundsException() } return ret;
Example 8:
A wrapping method that forwards the call to a set of replicas (e.g. to
implement a fault-tolerance aspect).
Object ret = null; // replicas is a RemoteRef[] field of the wrapper for (int i = 0; i < replicas.length; i++) { replicas[i].invoke(method(), args()); } return proceed();
Example 9:
A wrapping method that forwards the
call to a remote object (acts like a proxy). Note that this
wrapper does not call the nextWrapper method so that the
wrappee is never called.
return remoteRef.invoke ( method(), args() );
Example 10:
A wrapping method that caches the
results of the wrappee and that uses the cached values when the
arguments and the wrappee state have already been used.
Object ret = null; if (isCacheHit(wrappee(), args())) { ret = getCachedValue(wrappee(), args()); } else { ret = proceed(); setCachedValue(wrappee(), args(), ret); } return ret;
A role method is a regular public method of a wrapper.
public ret_type myRoleMethod ( user_defined_args... );
The goal of the role method is to extend the wrappee functionalities. A role method can be invoked on a wrappee (interface core.Wrappee seamlessly supported by any wrappable object) as follows:
ret = o.invokeRoleMethod( "myRoleMethod", user_defined_args_values );
Within the runtime context of a role method, the programmer can access several informations about the current method call as defined in jac.core.CollaborationParticipant.
We will not give any precise example of role methods since role methods are exactly regular methods except that they are implemented in wrappers. Using role method is a great alternative to inheritance since they can be dynamically added or removed on a per-object basis. As an example, if you want to implement a transactional aspect, you could add prepare, commit and rollback role methods to the set of objects that need to participate a transaction (even if they do not belong to the same class).
An exception handling method is a regular method (of a wrapper) that presents a prototype which is the following:
public Object myHandler ( AnException e );
Within its runtime context, wrappee() is a reference to the object that is wrapped. The method() and args() are the wrappee method where the exception occurs and the arguments with which it was called.
The exception handling method is automatically notified if one of the method called by the wrappee raises an exception that is of the type of the one declared in the exception handling method.
Exception handlers are really useful when programming aspects since some exceptions can be raised by the wrappers (so that they are not declared in the throws clause of the wrappee interface and are consequently not handled by the client). For instance, in the wrapping method examples 2, 3, 6, and 7, the wrapping methods throw some exceptions that will stop the program execution if they are not handled.
As an example, here is an exception handling method for the 7th sample. This exception handler should wrap the clients of the 7th wrapper wrappees. To make it not so trivial than just printing a error message, this handler retries once to call the originally called method when it receives an RetOutOfBoundsException. This can be an implementation basis for fault-detection and tolerance aspect for an application that needs it.
public class RetCheckingWrapper { int nRetry = 0; public Object handler( RetOutOfBoundsException e ) { Object ret = null; // retry to call the method once nRetry ++; if (nRetry < 1) { ret = wrappee().callMethod ( method(), args() ); } else { System.out.println ("We retried once but there is still a " + e); throw new InconsistencyError( wrappee(), method(), args() ); } return ret; } }
Even if the caught exception is not raised by an aspect, exception handlers can be useful when you want to have a clean code that is not polluted by catching all the runtime exceptions that can be thrown by the environment. For instance, you can wrap all the object of the application by exception handlers that catch the file system full exceptions so that your application is secure and readable.
To be aspect compliant, the reflective code must use the jac.core.rtti package instead of the
java.lang.reflect
one.
The JAC system offers the ability to introspect objects by using the jac.core.ObjectRepository class.
Moreover, JAC allows the programmer to access the running collaboration. A running context is composed of the interactions stack (one per thread) and a set of attributes (that can be dynamically defined at runtime). The collaboration propagates with the method calling flow.
The attributes are part of the collaboration and can be set in any object that implements the jac.core.CollaborationParticipant interface, i.e. the wrappers and the aspect components. When an attribute has been set, all the subsequently called methods will also have access to this attribute value. For instance:
// no attributes are defined... ... // defines an attribute 'a' attrdef ( "a", "aValue1" ); // "a" is equal to "aValue1" for all the following calls attr ( "a" ); // for all execution flows that goes through this, "a" will be equal // "aValue2" it will still equals "aValue1" for the // rest of the program. attrdef ( "a", "aValue2" );
A pointcut is an object that belongs to an aspect component and that is able to be parametrized to automatically wrap or perform aspect-actions on a set of base-objects regarding four pointcut expressions.
A pointcut expression is an expression based on regular expressions but that can include other keywords or operators as it will be explained later on. For a given method of any base object, the pointcut is activated if all the pointcut expressions match.
Thus, all the methods of all the objects in the system are checked once by the pointcut. A given method will be extended by the pointcut if:
The recommanded entry point to use poincuts are the factory methods
of the aspect components that allow the programmer to create
relevant pointcuts (see the pointcut
methods in jac.core.AspectComponent
Here is a sample of use of the pointcut feature for a tracing aspect.
If you got a tracing wrapper such as:
public class TracingWrapper extends Wrapper { public Object verboseCall() { System.out.println(method()+" is called"); Object ret = proceed(); System.out.println(method()+" is returning "+ret); } }
Then, the handy way of programming a verbose aspect that traces all the calls within the system is:
public class Tracing_1_AC extends AspectComponent { TracingWrapper wrapper = new TracingWrapper(); public void whenUsingNewInstance() { wrappee().wrapAll( wrapper, "verboseCall" ); } }
This is nice but very low-level since you have to overload the
whenUsingNewInstance
method and you have to know the
Wrappee
interface to wrap the object by hand. Moreover, if you
want to make more subtil filters on which objects or methods to wrap,
it becomes tedious. For instance, imagine you want to make verbose
only one instance called "o1", and one method of this instance called
"m1":
public class Tracing_2_AC extends AspectComponent { TracingWrapper wrapper = new TracingWrapper(); public void whenUsingNewInstance() { String name = NameRepository.get().getName(wrappee()); if( name.equals( "o1" ) ) { wrappee().wrap( wrapper, "verboseCall", "m1" ); } } }
Pointcuts offer an interesting, powerful, and simple alternative to
this technique (that can still be used if pointcuts do not work). Let us
rewrite Tracing_1_AC
and Tracing_2_AC
:
public class Tracing_1_AC extends AspectComponent { Tracing_1_AC() { pointcut(".*",".*",".*",TracingWrapper.class.getName(), "verboseCall",false); } }
public class Tracing_2_AC extends AspectComponent { Tracing_2_AC() { pointcut("o1",".*","m1.*",TracingWrapper.class.getName(), "verboseCall",false); } }
Please note that all the concision of the pointcut notation comes from the regular expression syntax that is very powerful (we use GNU regexp for the actual implementation). The point is that you must learn regular expressions to use pointcuts.
Let us recall that the naming aspect of JAC names the objects that are created in the program with the following convention:
newObject.getClass().getShortName().lowerCase() + newObject.getClass().getInstanceCount()
Thus, if newObject
is of the
jac.samples.Calcul
, then the first created calcul
instance name is calcul0
, the second is
calcul1
, and so on.
When you want to weave a given aspect to some objets, you can refer their names instead of their class. For instance, in a pointcut definition, writing:
object expression = ".*" class expression = "jac.samples.Calcul"
Is equivalent to:
object expression = "calcul[0-9]*" class expression = ".*"
But the point here is to be able to treat objects differently even if they are instances of the same class. For instance, the following pointcut expressions activates the pointcut *ONLY* on the three first calcul instances (and not on the other instances).
object expression = "calcul[0-2]" class expression = ".*"
In simple cases, you use objects as components and you instantiate (or bind to), a set of well-known objects with simple and predictible creation order so that it is simple to know their names.
However, in more dynamic cases, when your program instantiates objects regarding a more complex algorithm, you may be in trouble to find out the name of a particular object and it might be difficult or impossible to write the right pointcut expression with a regular expression.
A first simple means to overcome this issue is to custom the naming of the created objects so that the naming conventions are relevant and you can refer to an object unambigously. This can be done by telling the naming aspect to force the name of the objects.
For instance, imagine a double loop that dynamically creates objets:
class MyContainer { Vector elements = new Vector(); public addElement( MyElement element ) { elements.add( element ); } } class MyElement {...} [...] for( int i=0; i<max1; i++ ) { MyContainer container = new MyContainer(); for( int j=0; j<max2; j++ ) { container.addElement( new MyElement() ); } }
Now, imagine that you want to wrap each first element of the
containers. You are in trouble to know its name since
max1
and max2
are only known at
runtime (and can take any value depending on the
program). So a way to solve the problem is to force the
names of the elements:
[...] for( int i=0; i<max1; i++ ) { MyContainer container = new MyContainer(); for( int j=0; j<max2; j++ ) { NamingAC.forceName("element_"+i+"_"+j); container.addElement( new MyElement() ); } }
It then becomes easy to write an object pointcut-expression that matches only the first elements of the containers:
"element_[0-9]*_0"
However, this solution is not really satisfying for AOP since your base program now handles a naming issue. In some cases, this dependency can be avoided. JAC provides an original feature to do this called the Object-Paths (OPaths). With the OPath feature, you can access objects by traveling through the relations. This feature was widely inspired from XPaths.
An OPath is of the following form:
objectExpr/relationExpr/objectExpr/relationExpr/ ... /objectExpr
Each sub-expression of an OPath is separated from the next one
using /
is either an object regular
expression, either an relation regular expression
where:
objectExpr: an expression that matches a set of objects in the path's context (the root of the path matches in all the objects of the system) - it can be an index (e.g. 0 matches the first object in the path's context) relationExpr: an expression that matches all the references and all the collections of the objects set matched by the parent path expression
For instance, if you want to match all the users that own an
account within a bank called bank0
, then
you simply write:
bank0/accounts/.*/owners/.*
Finally, in our previous containers-elements sample, you can denote all the first elements of the containers so that configuring the names is not needed anymore:
mycontainer[0-9]*/elements/0
In method pointcut-expressions, you can use keywords to very easily denote sets of methods.
For instance, to match all the setters of a class, instead of having to write the following regular expression on the method pointcut-expression:
set.*([^,]+):void // all the methods that start with "set" // and take only one argument (no comma // in the parameter types)
You can simply write:
SETTERS
There are two reasons for using keywords in method pointcut-expressions. First it is simplier and you do not need to be a "god" in regular expressions. Second, you do not need to rely on naming conventions anymore!! Indeed, you can ask, for instance, to retrieve all the methods that modify the object's state (even if the names do not follow any conventions). This is possible because of the RTTI aspect (see jac.core.rtti). In the future, some of the RTTI configurations will be automatically performed through bytecode analysis, which will make JAC easier to use.
Here are the implemented keywords (some can take arguments):
ALL
: equivalent to the .*
regular
expressionMODIFIERS
: all the methods that modify the object's
state (write a field) -- including the settersACCESSORS
: all the methods that access the object's
state (read a field) -- including the gettersSETTER(fieldname)
: the setter for the field
called fieldname
GETTER(fieldname)
: the getter for the field
called fieldname
A pointcut expression can be composed of pointcut sub-expressions (expressions of the over-depicted forms) composed with the && (logical and), || (logical or), and the ! (NOT) operators. So, let's give some configuration example of the tracing aspect that uses pointcut expressions as an input for the "trace" configuration method:
// traces all the calls on all the objects of the system trace ".*" ".*" ".*"
// traces all the calls on all the objects of the system (alt.) trace "ALL" "ALL" "ALL"
// traces all the calls on all the methods of all the instances of // classes A and B trace ".*" "A || B" ".*"
// traces all the calls on all the methods of the first instances // of classes A and B (their names ends with 0) trace ".*0" "A || B" ".*"
// restricts the trace to the f field setters and the f field // getter trace ".*0" "A || B" "GETTER(f) || SETTER(f)"
// restricts the trace to all the methods minus the methods that // modify the state of the instances trace ".*0" "A || B" ".* && !MODIFIERS"
// traces all the instances of all classes except a0 and only the // methods that read the state of the instances or the getName // method trace ".* && !a0" ".*" "ACCESSORS || getName():String"
Note: parenthesis are not yet available in this release (<= 5.1). Do not try it.
The hello world example of Aspect-Oriented Programming is often the trace example where the aspect prints on the screen the methods that are called in the base program. The following steps show how to program this simple aspect and how to configure and weave it to a simple application.
Step 1: programming or reusing the wrappers
This
aspect is really simple and only needs one wrapper that can be
found in the jac.wrapper
package (you can copy it
and adapt it to the kind of trace you want to see):
import jac.core.*; import java.util.*; public class VerboseWrapper { public Object printCallingInfos() { System.out.println( "<<< Calling '" + method() + " on '" + wrappee().toString() + "' with " + Arrays.asList(args()) + " >>>" ); Object ret = proceed(); System.out.println( "<<< Returning from '" + method() + " on '" + wrappee().toString() + "' with " + Arrays.asList(args()) + " >>>" ); return ret; } }
Step 2: programming an aspect component
To weave the trace aspect, you must define an aspect component
that wraps all the methods of all the base objects that need to
be verbose. To specify the objects that need to be verbose, the
aspect programmer should add some configuration capability of
the aspect component. Here the configuration of the AC can be
done with the addVerboseClass
that allows the user
to specify that all the instances of the classes that are added
to the verboseClasses
list are verbose.
Simple version
import jac.core.*; class TracingAC extends AspectComponent { VerboseWrapper wrapper = new VerboseWrapper(); Vector verboseClasses = new Vector(); public void addVerboseClass( String className ) { verboseClasses.add( className ); } public void whenUsingNewInstance() { if( isVerboseClass( wrappee().getClass() ) ) { wrappee().wrapAll( wrapper, "printCallingInfos"); } } protected boolean isVerboseClass( String className ) { Iterator it = verboseClasses.iterator(); while( it.hasNext() ) { if( it.next().equals( className ) ) return true; } return false; } }
An alternative to the redefinition of the
whenUsingNewInstance
method is to use the pointcut
feature provided by the aspect components (see jac.core.Pointcut). A pointcut can be defined
by any aspect component at construction-time or at
configuration-time and allows the aspect programmer to easily
denote a set of base objects to wrap and how they are
wrapped. Pointcuts can be added with the
AspectComponent.pointcut(...)
methods.
The following code is extracted from the tracing aspect that is furnished with the JAC distribution. It uses the pointcut feature to define a configuration method that allows the user to define which method must be traced in a very concise and precise way.
Pointcut-based version
import jac.core.*; public class TracingAC extends AspectComponent { public void addTrace( String wrappeeExpr, String wrappeeClassExpr, String wrappeeMethodExpr ) { pointcut( wrappeeExpr, wrappeeClassExpr, wrappeeMethodExpr, VerboseWrapper.class.getName(), "printCallingInfos", false ); }
The expressions given are pointcut expressions that are based on
POSIX regular expressions (see gnu regexp) enhanced
with some operators such as &&
, ||
,
and !
, and some keywords to denote methods sets
such as MODIFIERS
, ACCESSORS
,
GETTER(field)
, or SETTER(field)
.
Note: for the programmers that are familiar with AspectJ, they can notice that the pointcut semantics are quite different from the pointcut semantics of AspectJ. In JAC, a pointcut is not only the definition of a set of base-program points, but also the definition of how they are modified by the aspect (the wrapper and the wrapping method are specified).
Step 3: register the aspect component so that its name is
"tracing" (to do this, edit the
jac.prop
file of the root
directory of the distribution).
Step 4: program the base application
This aspect can
be applied to an application you have already
programmed. For instance, if the application is composed
of one class.
package hello; class Hello { public void printHello() { System.out.println("Hello!"); } }
Step 5: configure the aspect(s)
The configuration of
the aspect can be handled within the main
method by
calling the configuration methods of the aspect components (here
addVerboseClass
) but the programmer must then
re-compile this class each time a configuration changes. Thus,
the best way to configure an aspect is to specify a
configuration file for each aspect. Each line of the
configuration file represents a invocation on a configuration
method of the aspect. Here is the configuration file for the
tracing aspect of the hello application
(tracing.acc
).
# each line is: # methodName parameters (where each parameter is double-quoted and # arrays are of the form : { item1, item2, ... itemN } addVerboseClass hello.Hello;
If you use the pointcut-based version, then the configuration file can look like:
addTrace ".*" "myClass1" ".*(float,float).*"; addTrace "anObjectName" ".*" "set.*([^,]+):void"; addTrace "anObjectName" ".*" "MODIFIERS && !set.*([^,]+):void";
This configuration is more precise thanks to the pointcut
expressions. The first line tells that all the methods within
any instance of the class named "myClass"
and that
take 2 floats must be traced. The second line tells that all the
methods that begin with the "set"
prefix, that have
one and only one argument and that return nothing must be traced
(this expression matches all the setters of a single object
named anObjectName
by the naming aspect. The third
line uses a keyword to denote a set of methods, here the full
expression means "all the methods that modify the object's state
excluding all the setters.
Then, you can specify this file name to configure the tracing aspect (declared in your application descriptor).
Step 7: program the launcher and the application descriptor
file
Then you must write a launching class and an
application descriptor that registers
the application within the system and creates the supported
aspects configurations (here the tracing aspect) -- see section
Programming a JAC application's launcher/descriptor.
package hello; class Run { public static void main( String[] args ) { Hello hello = new Hello()(); hello.printHello(); } }
// hello.jac file applicationName: hello launchingClass: hello.Run aspects: \ tracing jac/samples/calcul/tracing.acc true
Then run the program.
jac hello.jac
You should see the trace printing around the hello method call.
The -G
option allows you to access the
administration GUI and to weave/unweave the tracing aspect
during the program execution.