Votre plug-in peut utiliser l'API JDT pour créer des classes ou des interfaces, ajouter des méthodes aux types existants ou modifier les méthodes qu'ils comportent déjà.
Le moyen le plus simple de modifier un objet Java est d'utiliser l'API de l'élément Java correspondant. Des techniques plus générales peuvent être utilisées pour travailler sur le code source brut d'un élément Java.
Le moyen le plus simple de générer une unité de compilation par programme est d'utiliser IPackageFragment.createCompilationUnit. Vous spécifiez le nom et le contenu de l'unité de compilation. Cette dernière est créée à l'intérieur du package et le nouvel élément ICompilationUnit est renvoyé.
Vous pouvez créer une unité de compilation de manière générique en créant une ressource fichier dont l'extension est ".java" dans le dossier approprié qui correspond au répertoire du package. JDT n'a pas connaissance de l'utilisation de la ressource générique, si bien que le modèle Java n'est pas mis à jour tant que les programmes d'écoute (ou "listeners") d'événements de modification ne reçoivent pas de notification et que ceux de JDT mettent à jour le modèle Java avec la nouvelle unité de compilation.
Les modifications de code source Java les plus simples peuvent être réalisées à l'aide de l'API de l'élément Java.
Par exemple, vous pouvez demander un type à une unité de compilation. Une fois en possession du IType, vous pouvez utiliser des protocoles tels que createField, createInitializer, createMethod ou createType pour ajouter des membres de code source au type. Le code source et les informations sur l'emplacement du membre sont fournis dans ces méthodes.
L'interface ISourceManipulation définit des manipulations de code source courantes pour les éléments Java. Elle comprend des méthodes permettant de renommer, de déplacer, de copier ou de supprimer un membre de type.
Vous pouvez modifier le code par manipulation de l'unité de compilation (ce qui modifie le IFile sous-jacent) ou en modifiant une copie en mémoire de l'unité de compilation appelée copie de travail.
La méthode getWorkingCopy permet d'obtenir une copie de travail à partir d'une unité de compilation. Notez que l'unité de compilation n'a pas besoin d'exister dans le modèle Java pour qu'une copie de travail soit créée. Lorsqu'elle n'a plus d'utilité, cette copie de travail doit être détruite par la personne qui l'a créée à l'aide de la méthode destroy.
Les copies de travail modifient un tampon en mémoire. La méthode getWorkingCopy() crée une mémoire tampon par défaut, mais les clients peuvent mettre en oeuvre leur propre mémoire tampon à l'aide de la méthode getWorkingCopy(IProgressMonitor, IBufferFactory, IProblemRequestor). Les clients peuvent manipuler directement le contenu de cette mémoire tampon. Auquel cas, ils doivent régulièrement synchroniser la copie de travail avec la mémoire tampon à l'aide de la méthode reconcile() ou reconcile(boolean,IProgressMonitor).
Enfin, une copie de travail peut être sauvegardée sur disque (en remplacement de l'unité de compilation d'origine) à l'aide de la méthode commit.
Par exemple le fragment de code suivant crée une copie de travail sur une unité de compilation à l'aide d'une fabrique de mémoire tampon personnalisée. Le fragment modifie la mémoire tampon, synchronise les modifications, valide les modifications sur disque et enfin, détruit la copie de travail.
// Obtenir l'unité de compilation d'origine ICompilationUnit originalUnit = ...; // Obtenir la fabrique de mémoire tampon IBufferFactory factory = ...; // Créer la copie de travail IWorkingCopy workingCopy = (IWorkingCopy)originalUnit.getWorkingCopy(null, factory, null); // Modifier la mémoire tampon et synchroniser IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(); // Valider les modifications workingCopy.commit(false, null); // Détruire la copie de travail workingCopy.destroy();
Les copies de travail peuvent également être partagées entre plusieurs clients. La méthode getSharedWorkingCopy permet de créer une copie de travail partagée et la méthode findSharedWorkingCopy permet ensuite d'obtenir cette copie. Une copie de travail partagée est donc indexée sur l'unité de compilation d'origine et sur une fabrique de mémoire tampon.
L'exemple ci-dessous montre comment Client 1 crée une copie partagée, Client 2 extrait cette copie de travail, Client 1 détruit la copie de travail et Client 2, qui tente d'extraire la copie partagée, constate qu'elle n'existe plus :
// Client 1 & 2 : Obtenir l'unité de compilation d'origine ICompilationUnit originalUnit = ...; // Client 1 & 2 : Obtenir la fabrique de mémoire tampon IBufferFactory factory = ...; // Client 1 : Créer la copie de travail partagée IWorkingCopy workingCopyForClient1 = (IWorkingCopy)originalUnit.getSharedWorkingCopy(null, factory, null); // Client 2 : Obtenir la copie de travail partagée IWorkingCopy workingCopyForClient2 = (IWorkingCopy)originalUnit.findSharedWorkingCopy(factory); // Il s'agit de la même copie de travail partagée assert workingCopyForClient1 == workingCopyForClient2; // Client 1 : Détruire la copie de travail partagée workingCopyForClient1.destroy(); // Client 2 : Tenter d'obtenir la copie de travail partagée et constater son absence workingCopyForClient2 = (IWorkingCopy)originalUnit.findSharedWorkingCopy(factory); assert workingCopyForClient2 == null;
Vous pouvez créer intégralement une CompilationUnit à l'aide des méthodes de fabrique sur AST. Le noms de ces méthodes commencent par new.... Voici un exemple de création d'une classe HelloWorld.
Le premier fragment de code est la sortie générée :
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Le fragment qui suit est le code correspondant de génération de la sortie.
AST ast = new AST();
CompilationUnit unit = ast.newCompilationUnit();
PackageDeclaration packageDeclaration = ast.newPackageDeclaration();
packageDeclaration.setName(ast.newSimpleName("example"));
unit.setPackage(packageDeclaration);
ImportDeclaration importDeclaration = ast.newImportDeclaration();
QualifiedName name =
ast.newQualifiedName(
ast.newSimpleName("java"),
ast.newSimpleName("util"));
importDeclaration.setName(name);
importDeclaration.setOnDemand(true);
unit.imports().add(importDeclaration);
TypeDeclaration type = ast.newTypeDeclaration();
type.setInterface(false);
type.setModifiers(Modifier.PUBLIC);
type.setName(ast.newSimpleName("HelloWorld"));
MethodDeclaration methodDeclaration = ast.newMethodDeclaration();
methodDeclaration.setConstructor(false);
methodDeclaration.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
methodDeclaration.setName(ast.newSimpleName("main"));
methodDeclaration.setReturnType(ast.newPrimitiveType(PrimitiveType.VOID));
SingleVariableDeclaration variableDeclaration = ast.newSingleVariableDeclaration();
variableDeclaration.setModifiers(Modifier.NONE);
variableDeclaration.setType(ast.newArrayType(ast.newSimpleType(ast.newSimpleName("String"))));
variableDeclaration.setName(ast.newSimpleName("args"));
methodDeclaration.parameters().add(variableDeclaration);
org.eclipse.jdt.core.dom.Block block = ast.newBlock();
MethodInvocation methodInvocation = ast.newMethodInvocation();
name =
ast.newQualifiedName(
ast.newSimpleName("System"),
ast.newSimpleName("out"));
methodInvocation.setExpression(name);
methodInvocation.setName(ast.newSimpleName("println"));
InfixExpression infixExpression = ast.newInfixExpression();
infixExpression.setOperator(InfixExpression.Operator.PLUS);
StringLiteral literal = ast.newStringLiteral();
literal.setLiteralValue("Hello");
infixExpression.setLeftOperand(literal);
literal = ast.newStringLiteral();
literal.setLiteralValue(" world");
infixExpression.setRightOperand(literal);
methodInvocation.arguments().add(infixExpression);
ExpressionStatement expressionStatement = ast.newExpressionStatement(methodInvocation);
block.statements().add(expressionStatement);
methodDeclaration.setBody(block);
type.bodyDeclarations().add(methodDeclaration);
unit.types().add(type);
private int[] getOperatorPosition(Expression expression, char[] source) { if (expression instanceof InstanceofExpression) { IScanner scanner = ToolFactory.createScanner(false, false, false, false); scanner.setSource(source); int start = expression.getStartPosition(); int end = start + expression.getLength(); scanner.resetTo(start, end); int token; try { while ((token = scanner.getNextToken()) != ITerminalSymbols.TokenNameEOF) { switch(token) { case ITerminalSymbols.TokenNameinstanceof: return new int[] {scanner.getCurrentTokenStartPosition(), scanner.getCurrentTokenEndPosition()}; } } } catch (InvalidInputException e) { } } return null; }IScanner est utilisé pour diviser la source d'entrée en jetons. Chaque jeton à une valeur spécifique définie dans l'interface ITerminalSymbols. Il suffit ensuite de parcourir les jetons et d'extraire celui qui convient. Nous vous recommandons également d'utiliser le scanneur pour trouver la position du mot clé super dans un SuperMethodInvocation.
Certaines modifications de code source ne sont pas réalisables via l'API de l'élément Java. Il existe des techniques d'édition plus générales (par exemple, pour modifier le code source d'éléments existants) qui consistent à utiliser le code source brut et le DOM Java de l'unité de compilation.
Parmi ces techniques figure la suivante :
// Obtenir la source d'une unité de compilation String contents = myCompilationUnit.getBuffer().getContents(); // Créer un JDOM éditable myJDOM = new DOMFactory(); myDOMCompilationUnit = myJDOM.createCompilationUnit(contents, "MyClass"); // Naviguer dans et éditer la structure de l'unité de compilation à l'aide du // protocole de noeud JDOM. ... // Une fois les modifications apportées à tous les noeuds, // récupérer la source à partir du noeud DOM de l'unité de compilation. String newContents = myDOMCompilationUnit.getContents(); // Remettre ce code en place dans l'élément de l'unité de compilation myCompilationUnit.getBuffer().setContents(newContents); // Sauvegarder le tampon dans le fichier. myCompilationUnit.save(null, false);
Avec cette technique, il est possible que les marqueurs de problèmes soient associés à des numéros de ligne incorrects, puisque les éléments Java n'ont pas été mis à jour directement.
Le modèle consistant à modifier l'élément Java ne permet pas d'accéder à un niveau plus bas que celui des méthodes et des champs. L'arbre de syntaxe abstraite utilisé par le compilateur n'est pas disponible en tant qu'API. Par conséquent, les techniques utilisées par JDT pour décomposer la source en structures programmatiques ne sont pas actuellement disponibles sous forme d'API.
Si votre plug-in doit avoir connaissance des modifications apportées aux éléments Java une fois que celles-ci ont eu lieu, vous pouvez enregistrer un IElementChangedListener avec JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Vous pouvez être plus précis et indiquer le type d'événement qui vous intéresse à l'aide de addElementChangedListener(IElementChangedListener, int).
Par exemple, si seuls les événements qui se produisent avant exécution des compilateurs vous intéressent, utilisez :
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.PRE_AUTO_BUILD);
JavaCore prend en charge trois types d'événements :
D'un point de vue conceptuel, les listeners de modifications d'éléments Java sont similaires aux listeners de modifications de ressources (décrits dans la section Suivi des modifications de ressources). Le fragment de code suivant implémente un "rapporteur" de modification d'élément Java qui imprime les deltas de l'élément sur la console système.
public class MyJavaElementChangeReporter implements IElementChangedListener { public void elementChanged(ElementChangedEvent event) { IJavaElementDelta delta= event.getDelta(); if (delta != null) { System.out.println("delta received: "); System.out.print(delta); } } }
L'objet IJavaElementDelta inclut l'élément qui a été modifié ainsi que des indicateurs décrivant le type de modification qu'il a subi. La plupart du temps l'arborescence du delta est localisée au niveau du modèle Java. Les clients doivent alors naviguer dans ce delta avec getAffectedChildren pour trouver quels projets ont été modifiés.
L'exemple de méthode ci-dessous traverse une arborescence de deltas et imprime les éléments ajoutés, supprimés et modifiés :
void traverseAndPrint(IJavaElementDelta delta) { switch (delta.getKind()) { case IJavaElementDelta.ADDED: System.out.println(delta.getElement() + " was added"); break; case IJavaElementDelta.REMOVED: System.out.println(delta.getElement() + " was removed"); break; case IJavaElementDelta.CHANGED: System.out.println(delta.getElement() + " was changed"); if ((delta.getFlags() & IJavaElementDelta.F_CHILDREN) != 0) { System.out.println("The change was in its children"); } if ((delta.getFlags() & IJavaElementDelta.F_CONTENT) != 0) { System.out.println("The change was in its content"); } /* D'autres indicateurs peuvent aussi être vérifiés */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Plusieurs types d'opérations peuvent déclencher une notification de modification d'élément Java, notamment :
Comme pour IResourceDelta les deltas de l'élément Java peuvent être regroupés en lot à l'aide de IWorkspaceRunnable. Les deltas issus de plusieurs modèles Java exécutés dans le cadre d'un IWorkspaceRunnable sont fusionnés et signalés en même temps.
JavaCore fournit une méthode d'exécutionpour les modifications d'élément Java par lots.
Par exemple, le fragment de code suivant déclenche 2 événements de modification d'élément Java :
// Obtenir le package IPackageFragment pkg = ...; // Créer 2 unités de compilation ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Tandis que le fragment de code suivant déclenche un événement de modification d'élément Java :
// Obtenir le package IPackageFragment pkg = ...; // Créer 2 unités de compilation JavaCore.run( new IWorkspaceRunnable() { public void run(IProgressMonitor monitor) throws CoreException { ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null); } }, null);