Jump to: navigation, search

Difference between revisions of "Developer's guide to building tools on top of AJDT and AspectJ"

m
 
(16 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
This page is intended to aid anyone developing tools to extend or work with AJDT/AspectJ. Please contribute to this page with any relevant information, such as example code using the AJDT and/or AspectJ APIs.
 
This page is intended to aid anyone developing tools to extend or work with AJDT/AspectJ. Please contribute to this page with any relevant information, such as example code using the AJDT and/or AspectJ APIs.
  
--[[User:Mpchapman.gmail.com|Mpchapman.gmail.com]] 06:12, 8 January 2007 (EST)
+
This page is out of date. Our intention is to update this page for AJDT 1.6.1, but we have not had time for this yet. Please understand that some of what is on this page may no longer be correct.  If you have any questions, please send them to the mailing list [https://dev.eclipse.org/mailman/listinfo/ajdt-dev ajdt-dev].
 +
 
  
 
----
 
----
 
  
 
== Obtaining crosscutting relationship information from AJDT ==
 
== Obtaining crosscutting relationship information from AJDT ==
  
(the following is derived from a post to the AJDT newsgroup on 26/10/2006)
+
If you are developing an eclipse plugin and require access to crosscutting information whenever a project is built, you can register a listener with AJDT.
 
+
Your plug-in will need to depend on org.eclipse.ajdt.core, org.eclipse.core.resources and org.eclipse.jdt.core, and org.aspectj.weaver. In the org.eclipse.ajdt.core plug-in there is an IAdviceChangedListener interface with a single adviceChanged() method.
If you're developing an eclipse plugin and require access to crosscutting information whenever a project is built, you can register a listener with AJDT.
+
Your plug-in will need to depend on org.eclipse.ajdt.core, org.eclipse.core.resources and org.eclipse.jdt.core. In the org.eclipse.ajdt.core plug-in there is an IAdviceChangedListener interface with a single adviceChanged() method.
+
  
 
Register this with the AJBuilder class like this (in your plug-in's start() method for example):  
 
Register this with the AJBuilder class like this (in your plug-in's start() method for example):  
Line 17: Line 15:
 
     AJBuilder.addAdviceListener(new MyAdviceListener());  
 
     AJBuilder.addAdviceListener(new MyAdviceListener());  
  
Currently (AJDT 1.4) this is called after every build of an AspectJ project (i.e. every *potential* advice change). In a future release this may be optimized to be only called if the advice has actually changed. AJDT/UI uses this mechanism to update the orange arrow image decorator.  
+
Currently (AJDT 1.6) this is called after every build of an AspectJ project (i.e. every *potential* advice change). In a future release this may be optimized to be only called if the advice has actually changed. AJDT/UI uses this mechanism to update the orange arrow image decorator.  
  
Crosscutting information can then be obtained from the AJModel class. Here's an example:  
+
Crosscutting information can then be obtained from the AJProjectModelFacade class. Here's an example with some pseudo code you can adapt:
 +
<source lang="java">
 +
public class MyAdviceListener implements IAdviceChangedListener {
 +
  // get the project
 +
  IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject("MyProject");
 +
 +
  public void adviceChanged() {
 +
 +
    // the AJProjectModelFacade is a facade into the AspectJ's model.  Provides functionality
 +
    // to query the AspectJ cross cutting model as well as to translate back and forth between
 +
    // JDT elements and AspectJ elements
 +
    AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForProject(project);
 +
 +
    // You can narrow this to include on certain kinds of relationships
 +
    AJRelationshipType[] relTypes = AJRelationshipManager.getAllRelationshipTypes();
 +
 +
    // check first to see if there is a model
 +
    // will return false if there has not yet been a successful build of this project
 +
    if (model.hasModel()) {
 +
 +
      // all relationships for project
 +
      // can also query for relationships on individual elements or compilation units
 +
      List<IRelationship> rels = (List<IRelationship>) model.getRelationshipsForProject(relTypes);
 +
      for (IRelationship rel : rels) {
 +
        // Source is an IProgramElement handle, which refers to a piece of the program in AspectJ's World
 +
        IJavaElement source = model.programElementToJavaElement(rel.getSourceHandle());
 +
        // do something with the source...
 +
        System.out.println(source.getElementName());
 +
 +
        for (String targetHandle : (Iterable<String>) rel.getTargets()) {
 +
          // Can have multiple targets
 +
          IJavaElement target = model.programElementToJavaElement(targetHandle);
 +
          // do something with the target...
 +
          System.out.println(target.getElementName());
 +
        }
 +
      }
 +
    }
 +
  }
 +
}
 +
</source>
  
  public class MyAdviceListener implements IAdviceChangedListener {
+
Several notes about this:
    public void adviceChanged() {
+
        IProject project = AspectJPlugin.getDefault()
+
                .getCurrentProject();
+
        System.out.println("build detected for project: " + project);
+
        AJRelationshipType[] relsTypes = AJRelationshipManager
+
                .getAllRelationshipTypes();
+
        List rels = AJModel.getInstance().getAllRelationships(
+
                project, relsTypes);
+
        for (Iterator iter = rels.iterator(); iter.hasNext();) {
+
            AJRelationship ajrel = (AJRelationship) iter.next();
+
            System.out.println("Relationship: "
+
                    + ajrel.getSource().getElementName() + " "
+
                    + ajrel.getRelationship().getDisplayName() + " "
+
                    + ajrel.getTarget().getElementName());
+
        }
+
    }
+
  }
+
  
For the "TJP Example" project (File > New > Other > AspectJ > AspectJ Examples), this prints:  
+
#The API may have some minor changes in the future.  Please send a message to the [https://dev.eclipse.org/mailman/listinfo/ajdt-dev ajdt-dev mailing list] if anything on this page is out of date.
 +
#The '''AJProjectModelFacade''' object is a lightweight entrance into the AspectJ world.  It is only valid until the next build.  So, don't store them.  Use them and dispose as needed.
 +
#'''AJProjectModelFacade''' objects only contain data after the first successful build.  You can call the '''hasModel()''' method to see if an AspectJ model exists for the project.
 +
#As you can see, you get the relationship in both directions. See '''AJRelationshipManager''' for the full list of relationships, so you can just ask for the relationship types you're interested in.
 +
#'''IRelationship.getSourceHandle()''' and '''IRelationship.getTargets()''' return Strings that represent AspectJ element handles.  You can use the following AJProjectModelFacade methods to convert to model elements:
 +
#*'''toProgramElement(String)''' --- returns '''IProgramElement'''.  From here you can obtain information about the pointcut, intertype element, or declare element.
 +
#*'''programElementToJavaElement(String)''' or '''programElementToJavaElement(IProgramElement)''' --- returns '''IJavaElement'''.  From here you can hook into JDT tooling.
 +
#There is no requirement to register an advice changed listener.  You can get access to the crosscutting model at any time (as long as the project has had a successful build) using the following code:
  
  build detected for project: P/TJP Example
+
<source lang="java">
   Relationship: around advises main
+
IProject project =
  Relationship: around advises foo
+
   ResourcesPlugin.getWorkspace().getRoot().getProject("MyProject");
  Relationship: around advises bar
+
AJProjectModelFacade model =
  Relationship: bar advised by around
+
  AJProjectModelFactory.getInstance().getModelForProject(project);
  Relationship: foo advised by around
+
</source>
  Relationship: main advised by around
+
 
+
As you can see, you get the relationship in both directions. See AJRelationshipManager for the full list of relationships, so you can just ask for the relationship types you're interested in.  
+
 
+
AJRelationship.getSource() and getTarget() return instances of IJavaElement so you can obtain further information from that, such as the parent or underlying resource.
+
  
 
----
 
----
 
  
 
== Compilation Units in AJDT ==
 
== Compilation Units in AJDT ==
Line 65: Line 86:
 
From an AJCompilationUnit you can obtain various structural information such as getAllTypes(). The primary type for ".aj" files is typically an aspect, which is represented by the AspectElement class, which contains aspect-specific methods such as getPointcuts() and getAdvice(). These return further aspect-specific elements such as PointcutElement and AdviceElement.
 
From an AJCompilationUnit you can obtain various structural information such as getAllTypes(). The primary type for ".aj" files is typically an aspect, which is represented by the AspectElement class, which contains aspect-specific methods such as getPointcuts() and getAdvice(). These return further aspect-specific elements such as PointcutElement and AdviceElement.
  
Since AJDT 1.4 for Eclipse 3.2, we register the ".aj" file extension as containing "Java-like" source code. One side-effect of this is that JDT creates its own compilation unit for a .aj file, in addition to the one created by AJDT. This is usually empty, as JDT doesn't understand aspects. If you obtain a compilation unit for a .aj file, you may therefore need to ensure it is an AJCompilationUnit - the method AJCompilationUnitManager.mapToAJCompilationUnit() can be used for this. Also, in order to distinguish the two compilation units, they have different working copy owners (and the JDT ones are filtered from the package explorer). This means for example that IType.resolveType(String) won't work for aspects - instead you need to use the two arg version, IType.resolveType(String, WorkingCopyOwner), specifying AJDT's working copy owner:
+
Since AJDT 1.6.2 for Eclispe 3.4, we use the [[JDT weaving features | Eclipse weaving service]] to weave into JDT. One set of join points that are advised are the ones related to the creation of CompilationUnit objects. If the file has is *.aj file, AJCompilationUnit is created instead of a standard Java CompilationUnit.
  
  AJCompilationUnitManager.defaultAJWorkingCopyOwner()
 
  
 +
=== Getting the contents of an AJCompilationUnit ===
  
----
+
Because JDT expects that all source it works with is true Java code, JDT does not work well with AspectJ.  In order to get around this, AJCompilationUnits maintain two buffers that contain source contents.  The first is a ''java compatible'' buffer and the second is the ''original content'' buffer.  The ''java compatible'' buffer is the buffer that is returned by default when AJCompilationUnit.getContents() is called.  This buffer contains the AspectJ code with all aspect-specific syntax stripped out.  The ''original content'' buffer contains (as you would expect) the original content of the file.
  
 +
For example if the ''original content'' buffer looks like:
 +
 +
aspect Foo {
 +
  pointcut f() : execution(* foo());
 +
}
 +
 +
the ''Java compatible'' buffer becomes
 +
class  Foo {
 +
  pointcut f()                    ;
 +
}
 +
 +
Notice that the source locations of the identifiers are the same in both buffers.  This ensures that reference finding and hyperlinking works as expected.
 +
 +
If you require working with the original content of an AspectJ CompilationUnit ''ajUnit'', you can do the following:
 +
 +
<source lang="java">
 +
char[] contents;
 +
synchronized(ajUnit) {
 +
  try {
 +
      ajUnit.requestOriginalContentMode();
 +
      contents = ajUnit.getContents();
 +
  } finally {
 +
      // be sure to call this method as soon as you are done.
 +
      ajUnit.discardOriginalContentMode(); 
 +
  }
 +
}
 +
// do something with content
 +
</source>
 +
 +
What this method does asks for the AJCU to temporarily switch its buffer to the AJ buffer from the Java buffer.  It is best to do this in a synchronized block so that you don't risk other threads coming by and accidentally using the wrong buffer (AJDT itself doesn't use a
 +
synchronized block for this, but it should).
 +
 +
----
  
 
== Using the AspectJ AST parser ==
 
== Using the AspectJ AST parser ==
Line 110: Line 164:
  
 
Limitation: There is currently no AST support for resolving type bindings:
 
Limitation: There is currently no AST support for resolving type bindings:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=146528
+
[https://bugs.eclipse.org/bugs/show_bug.cgi?id=146528 bug 146528]
  
  
 
----
 
----
  
----
+
== The interface tools are expected to use to drive the AspectJ compiler ==
 +
 
 +
Work has gone on as part of [https://bugs.eclipse.org/bugs/show_bug.cgi?id=148190 bug 148190] to update the interface tools are expected to use to drive the AspectJ compiler. This has resulted in the 'core' ajde functionality (which drives the compiler and records progress) to be contained in a new ajde.core AspectJ project with the remaining functionality contained within the ajde project. 
 +
 
 +
For tools who wish to drive the compiler but manage their own ui representation an org.aspectj.ajde.core.AjCompiler instance is needed for each new configuration (project or build configuration). This is created by providing implementations of the other org.aspectj.ajde.core interfaces (IBuildMessageHandler, IBuildProgressMonitor, ICompilerConfiguration and IOutputLocationManager) and then either build() or buildFresh() need to be called on the AjCompiler to drive a compile. For an example of an implementation of this see the MultiProjectIncrementalTests class in the Aspectj tests project or the AJDT implementation in the org.eclipse.ajdt.core and org.eclispe ajdt.ui plugin projects.
 +
 
 +
For tools who wish to drive the compiler and do not want to manage the ui representation then org.aspectj.ajde.Ajde needs to be initialised. This is initialised with implementations of the same ui related interfaces (EditorAdapter etc) but also with the new ajde.core interfaces (IBuildMessageHandler, IBuildProgressMonitor, ICompilerConfiguration and IOutputLocationManager). There are helper methods within org.aspectj.ajde.Ajde: runBuildInSameThread(..) and runBuildInDifferentThread(..) which should be called when the user has selected to run a build. See the ajbrowser implementation for an example of this usage.
 +
 
 +
[[Category:AJDT]]

Latest revision as of 00:12, 25 November 2010

This page is intended to aid anyone developing tools to extend or work with AJDT/AspectJ. Please contribute to this page with any relevant information, such as example code using the AJDT and/or AspectJ APIs.

This page is out of date. Our intention is to update this page for AJDT 1.6.1, but we have not had time for this yet. Please understand that some of what is on this page may no longer be correct. If you have any questions, please send them to the mailing list ajdt-dev.



Obtaining crosscutting relationship information from AJDT

If you are developing an eclipse plugin and require access to crosscutting information whenever a project is built, you can register a listener with AJDT. Your plug-in will need to depend on org.eclipse.ajdt.core, org.eclipse.core.resources and org.eclipse.jdt.core, and org.aspectj.weaver. In the org.eclipse.ajdt.core plug-in there is an IAdviceChangedListener interface with a single adviceChanged() method.

Register this with the AJBuilder class like this (in your plug-in's start() method for example):

   AJBuilder.addAdviceListener(new MyAdviceListener()); 

Currently (AJDT 1.6) this is called after every build of an AspectJ project (i.e. every *potential* advice change). In a future release this may be optimized to be only called if the advice has actually changed. AJDT/UI uses this mechanism to update the orange arrow image decorator.

Crosscutting information can then be obtained from the AJProjectModelFacade class. Here's an example with some pseudo code you can adapt:

 public class MyAdviceListener implements IAdviceChangedListener { 
   // get the project
   IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject("MyProject");
 
   public void adviceChanged() {
 
     // the AJProjectModelFacade is a facade into the AspectJ's model.  Provides functionality
     // to query the AspectJ cross cutting model as well as to translate back and forth between
     // JDT elements and AspectJ elements
     AJProjectModelFacade model = AJProjectModelFactory.getInstance().getModelForProject(project);
 
     // You can narrow this to include on certain kinds of relationships
     AJRelationshipType[] relTypes = AJRelationshipManager.getAllRelationshipTypes();
 
     // check first to see if there is a model
     // will return false if there has not yet been a successful build of this project
     if (model.hasModel()) {
 
       // all relationships for project
       // can also query for relationships on individual elements or compilation units
       List<IRelationship> rels = (List<IRelationship>) model.getRelationshipsForProject(relTypes);
       for (IRelationship rel : rels) {
         // Source is an IProgramElement handle, which refers to a piece of the program in AspectJ's World
         IJavaElement source = model.programElementToJavaElement(rel.getSourceHandle());
         // do something with the source...
         System.out.println(source.getElementName());
 
         for (String targetHandle : (Iterable<String>) rel.getTargets()) {
           // Can have multiple targets
           IJavaElement target = model.programElementToJavaElement(targetHandle);
           // do something with the target...
           System.out.println(target.getElementName());
         }
       }
     }
   }
 }

Several notes about this:

  1. The API may have some minor changes in the future. Please send a message to the ajdt-dev mailing list if anything on this page is out of date.
  2. The AJProjectModelFacade object is a lightweight entrance into the AspectJ world. It is only valid until the next build. So, don't store them. Use them and dispose as needed.
  3. AJProjectModelFacade objects only contain data after the first successful build. You can call the hasModel() method to see if an AspectJ model exists for the project.
  4. As you can see, you get the relationship in both directions. See AJRelationshipManager for the full list of relationships, so you can just ask for the relationship types you're interested in.
  5. IRelationship.getSourceHandle() and IRelationship.getTargets() return Strings that represent AspectJ element handles. You can use the following AJProjectModelFacade methods to convert to model elements:
    • toProgramElement(String) --- returns IProgramElement. From here you can obtain information about the pointcut, intertype element, or declare element.
    • programElementToJavaElement(String) or programElementToJavaElement(IProgramElement) --- returns IJavaElement. From here you can hook into JDT tooling.
  6. There is no requirement to register an advice changed listener. You can get access to the crosscutting model at any time (as long as the project has had a successful build) using the following code:
IProject project =
  ResourcesPlugin.getWorkspace().getRoot().getProject("MyProject");
AJProjectModelFacade model =
  AJProjectModelFactory.getInstance().getModelForProject(project);

Compilation Units in AJDT

JDT creates compilation units (instances of ICompilationUnit) for .java files. AJDT creates compilation units for .aj files, which are instances of AJCompilationUnit (which implements ICompilationUnit). The class AJCompilationUnitManager (in the org.eclipse.ajdt.core plug-in) contains some useful methods relating to this, such as:

 public AJCompilationUnit getAJCompilationUnit(IFile file)

From an AJCompilationUnit you can obtain various structural information such as getAllTypes(). The primary type for ".aj" files is typically an aspect, which is represented by the AspectElement class, which contains aspect-specific methods such as getPointcuts() and getAdvice(). These return further aspect-specific elements such as PointcutElement and AdviceElement.

Since AJDT 1.6.2 for Eclispe 3.4, we use the Eclipse weaving service to weave into JDT. One set of join points that are advised are the ones related to the creation of CompilationUnit objects. If the file has is *.aj file, AJCompilationUnit is created instead of a standard Java CompilationUnit.


Getting the contents of an AJCompilationUnit

Because JDT expects that all source it works with is true Java code, JDT does not work well with AspectJ. In order to get around this, AJCompilationUnits maintain two buffers that contain source contents. The first is a java compatible buffer and the second is the original content buffer. The java compatible buffer is the buffer that is returned by default when AJCompilationUnit.getContents() is called. This buffer contains the AspectJ code with all aspect-specific syntax stripped out. The original content buffer contains (as you would expect) the original content of the file.

For example if the original content buffer looks like:

aspect Foo {
  pointcut f() : execution(* foo());
}

the Java compatible buffer becomes

class  Foo {
 pointcut f()                     ;
}

Notice that the source locations of the identifiers are the same in both buffers. This ensures that reference finding and hyperlinking works as expected.

If you require working with the original content of an AspectJ CompilationUnit ajUnit, you can do the following:

char[] contents;
synchronized(ajUnit) {
   try {
      ajUnit.requestOriginalContentMode();
      contents = ajUnit.getContents();
   } finally {
      // be sure to call this method as soon as you are done.
      ajUnit.discardOriginalContentMode();  
   }
}
// do something with content

What this method does asks for the AJCU to temporarily switch its buffer to the AJ buffer from the Java buffer. It is best to do this in a synchronized block so that you don't risk other threads coming by and accidentally using the wrong buffer (AJDT itself doesn't use a synchronized block for this, but it should).


Using the AspectJ AST parser

Basic example, taken from bug 88861

 import java.util.*;
 import org.aspectj.org.eclipse.jdt.core.dom.*;
 public class Program {
   public static void main(String []argv) {
     ASTParser parser = ASTParser.newParser(AST.JLS2);
     parser.setCompilerOptions(new HashMap());
     parser.setSource(argv[0].toCharArray());
     CompilationUnit cu2 = (CompilationUnit) parser.createAST(null);
     AjNaiveASTFlattener visitor = new AjNaiveASTFlattener();
     cu2.accept(visitor);
     System.err.println(visitor.getResult());
   }
 }

Compile the above and run it:

 java Program "public aspect X { pointcut p(): call(* *(..));}"

then it prints:

 public aspect X {
    pointcut p():call(* *(..));
 }

See also bug 110465: Continue AST work



Known limitations, bugs, and outstanding issues

Limitation: There is currently no AST support for resolving type bindings: bug 146528



The interface tools are expected to use to drive the AspectJ compiler

Work has gone on as part of bug 148190 to update the interface tools are expected to use to drive the AspectJ compiler. This has resulted in the 'core' ajde functionality (which drives the compiler and records progress) to be contained in a new ajde.core AspectJ project with the remaining functionality contained within the ajde project.

For tools who wish to drive the compiler but manage their own ui representation an org.aspectj.ajde.core.AjCompiler instance is needed for each new configuration (project or build configuration). This is created by providing implementations of the other org.aspectj.ajde.core interfaces (IBuildMessageHandler, IBuildProgressMonitor, ICompilerConfiguration and IOutputLocationManager) and then either build() or buildFresh() need to be called on the AjCompiler to drive a compile. For an example of an implementation of this see the MultiProjectIncrementalTests class in the Aspectj tests project or the AJDT implementation in the org.eclipse.ajdt.core and org.eclispe ajdt.ui plugin projects.

For tools who wish to drive the compiler and do not want to manage the ui representation then org.aspectj.ajde.Ajde needs to be initialised. This is initialised with implementations of the same ui related interfaces (EditorAdapter etc) but also with the new ajde.core interfaces (IBuildMessageHandler, IBuildProgressMonitor, ICompilerConfiguration and IOutputLocationManager). There are helper methods within org.aspectj.ajde.Ajde: runBuildInSameThread(..) and runBuildInDifferentThread(..) which should be called when the user has selected to run a build. See the ajbrowser implementation for an example of this usage.