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 un 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 ignorée par la personne qui l'a créée à l'aide de la méthode discardWorkingCopy.
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 implémenter leur propre mémoire tampon à l'aide de la méthode getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Les clients peuvent manipuler directement le contenu de cette mémoire tampon. Dans ce cas, ils doivent régulièrement synchroniser la copie de travail avec la mémoire tampon à l'aide de la méthode reconcile(int, boolean, WorkingCopyOwner, 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 commitWorkingCopy.
Par exemple le fragment de code suivant crée une copie de travail sur une unité de compilation à l'aide d'un propriétaire de copie de travail personnalisée. Le fragment modifie la mémoire tampon, synchronise les modifications, valide les modifications sur disque et enfin, ignore la copie de travail.
// Obtenir l'unité de compilation d'origine ICompilationUnit originalUnit = ...; // Obtenir le propriétaire de la copie de travail WorkingCopyOwner owner = ...; // Créer la copie de travail ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modifier la mémoire tampon et synchroniser IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Valider les modifications workingCopy.commitWorkingCopy(false, null); // Détruire la copie de travail workingCopy.discardWorkingCopy();
Les copies de travail peuvent également être partagées par plusieurs clients utilisant un propriétaire de copie de travail. Une copie de travail peut ensuite être extraite à l'aide de la méthode findWorkingCopy. Une copie de travail partagée est donc indexée sur l'unité de compilation d'origine et sur un propriétaire de copie de travail.
L'exemple ci-dessous montre comment Client 1 crée une copie partagée, Client 2 extrait cette copie de travail, Client 1 ignore 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 le propriétaire de la copie de travail WorkingCopyOwner owner = ...; // Client 1 : Créer la copie de travail partagée ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2 : Obtenir la copie de travail partagée ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Il s'agit de la même copie de travail partagée assert workingCopyForClient1 == workingCopyForClient2; // Client 1 : Ignorer la copie de travail partagée workingCopyForClient1.discardWorkingCopy(); // Client 2 : Tenter d'obtenir la copie de travail partagée et constater son absence workingCopyForClient2 = originalUnit.findWorkingCopy(owner); 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 l'API de réécriture de DOM/AST.
Pour effectuer la réécriture DOM/AST, il existe deux ensembles d'API : la réécriture descriptive et la réécriture de modification.
La réécriture descriptive ne modifie pas l'AST mais utilise l'API ASTRewrite pour générer les descriptions des modifications.
Le programme de réécriture AST rassemble les descriptions des modifications apportées aux noeud et les convertit
en modifications de texte pouvant être appliquées à la source d'origine.
// création d'un document
ICompilationUnit cu = ... ; // le contenu est "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// création d'un élément DOM/AST à partir d'une unité ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// création d'un élément ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// description de la modification
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// calcul des modifications de texte
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// calcul du nouveau code source
edits.apply(document);
String newSource = document.get();
// mise à jour de l'unité de compilation
cu.getBuffer().setContents(newSource);
La modification de l'API permet de modifier directement l'AST :
// création d'un document ICompilationUnit cu = ... ; // le contenu est "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // création d'un élément DOM/AST à partir d'une unité ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // démarrage de l'enregistrement des modifications astRoot.recordModifications(); // modification de l'AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // calcul des modifications de texte TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // calcul du nouveau code source edits.apply(document); String newSource = document.get(); // mise à jour de l'unité de compilation cu.getBuffer().setContents(newSource);
Si votre plug-in doit avoir connaissance des modifications apportées aux éléments Java une fois que ces dernières 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 lors d'une opération de réconciliation vous intéressent, utilisez :
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
JavaCore prend en charge deux types d'événements :
D'un point de vue conceptuel, les programmes d'écoute de modifications d'éléments Java sont similaires aux programmes d'écoute 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écution pour 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);