Il plug-in dell'utente può utilizzare l'API JDT per creare classi o interfacce, aggiungere metodi a tipi esistenti o modificare il metodo per i tipi.
Il modo più semplice per modificare oggetti Java consiste nell'utilizzo dell'API di elemento Java. Altre tecniche generiche possono essere utilizzate per gestire il codice sorgente primitivo di un elemento Java.
Il modo più semplice per creare in modo programmatico un'unità di compilazione consiste nell'utilizzo di IPackageFragment.createCompilationUnit. L'utente specifica il nome e il contenuto dell'unità di compilazione. L'unità di compilazione viene creata all'interno del pacchetto, quindi viene restituito il nuovo elemento ICompilationUnit.
Un'unità di compilazione può essere creata genericamente mediante la creazione di una risorsa file con estensione ".java" nella cartella appropriata corrispondente alla directory del pacchetto. L'utilizzo dell'API di risorse generiche è alla base della strumentazione Java,per cui il modello Java non viene aggiornato fino alla notifica dei listener di modifica delle risorse generiche e fino a quando il modello Java non viene aggiornato dai listener JDT con la nuova unità di compilazione.
Le modifiche più semplici al codice sorgente Java possono essere apportate utilizzando l'API di elemento Java.
Ad esempio, è possibile eseguire la query di un tipo da un'unità di compilazione. Dopo aver ottenuto l'interfaccia IType, è possibile utilizzare i protocolli come createField, createInitializer, createMethod, oppure createType per aggiungere membri di codice sorgente al tipo. Il codice sorgente e le informazioni relative alla posizione del membro vengono forniti in questi metodi.
L'interfaccia ISourceManipulation definisce le modifiche dell'origine comune per gli elementi Java. Sono compresi metodi per la ridenominazione, lo spostamento, la copia o l'eliminazione del membro di un tipo.
Per cambiare il codice è possibile modificare l'unità di compilazione(in questo modo l'interfaccia IFile viene modificata) oppure una copia in memoria dell'unità di compilazione denominata copia di lavoro.
Una copia di lavoro si ottiene da un'unità di compilazione mediante il metodo getWorkingCopy. Chiunque crei una tale copia di lavoro è responsabile della relativa distruzione quando non è più necessaria mediante il metodo destroy.
Le copie di lavoro modificano un buffer in memoria. Anche se il metodo getWorkingCopy() crea un buffer predefinito, i client possono fornire la propria implementazione del buffer mediante il metodo getWorkingCopy(IProgressMonitor, IBufferFactory, IProblemRequestor). I client possono modificare il testo di questo buffer direttamente. In tal caso, essi devono sincronizzare la copia di lavoro con il buffer volta per volta mediante il metodo reconcile() o il metodo reconcile(boolean,IProgressMonitor).
Infine, è possibile salvare una copia di lavoro su disco (sostituendo l'unità di compilazione originale) mediante il metodo commit.
Ad esempio il frammento di codice crea una copia di lavoro su un'unità di compilazione mediante una factory del buffer personalizzata. Il frammento modifica il buffer, riconcilia le modifiche, ne esegue il commit su disco e infine distrugge la copia di lavoro.
// Ottenere l'unità di compilazione originale ICompilationUnit originalUnit = ...; // Ottenere il factory del buffer IBufferFactory factory = ...; // Creare copia di lavoro IWorkingCopy workingCopy = originalUnit.getWorkingCopy(null, factory, null); // Modificare buffer e riconciliare IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(); // Apportare modifiche workingCopy.commit(false, null); // Distruggere la copia di lavoro workingCopy.destroy();
E' inoltre possibile che le copie di lavoro siano condivise da diversi client. Una copia di lavoro condivisa viene creata mediante il metodo getSharedWorkingCopy e può essere recuperata successivamente mediante il metodo findSharedWorkingCopy. Una copia di lavoro condivisa viene in questo modo basata su chiavi su un'unità di compilazione originale e su una factory di buffer.
Il codice seguente mostra in che modo il client 1 crea una copia di lavoro condivisa, il client 2 la recupera, il client 1 distrugge la copia di lavoro e il client 2 che tenta di recuperare la copia di lavoro condivisa nota che questa non è più presente:
// Client 1 & 2: Ottenere l'unità di compilazione originale ICompilationUnit originalUnit = ...; // Client 1 & 2: Ottenere il factory del buffer IBufferFactory factory = ...; // Client 1: Creare copia di lavoro condivisa IWorkingCopy workingCopyForClient1 = originalUnit.getSharedWorkingCopy(null, factory, null); // Client 2: Recuperare copia di lavoro condivisa IWorkingCopy workingCopyForClient2 = originalUnit.findSharedWorkingCopy(factory); // Questa è la stessa copia di lavoro assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Distruggere la copia di lavoro condivisa workingCopyForClient1.destroy(); // Client 2: Tentare di recuperare la copia di lavoro condivisa e individuare che sia null workingCopyForClient2 = originalUnit.findSharedWorkingCopy(factory); assert workingCopyForClient2 == null;
E' possibile creare una CompilationUnit da zero mediante i metodi di factory su AST. Tali metodi denominano l'inizio con new.... Di seguito è riportato un esempio che crea una classe HelloWorld.
Il primo frammento è l'output generato:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Il seguente frammento è il codice corrispondente che genera l'output.
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; }L'interfaccia IScanner viene utilizzata per dividere l'input in token. Ogni token presenta un valore specifico definito nell'interfaccia ITerminalSymbols. E' abbastanza semplice esplorare e recuperare il token giusto. Si consiglia di utilizzare lo scanner per ricercare la posizione della parola chiave super in SuperMethodInvocation.
Alcune modifiche del codice sorgente non sono fornite attraverso l'API di elemento Java. Un modo più generico per modificare il codice sorgente (ad esempio modificando il codice sorgente di elementi esistenti) consiste nell'utilizzo del codice sorgente primitivo dell'unità di compilazione e del DOM Java.
Tali tecniche comprendono quanto segue:
// ottenere il codice sorgente per un'unità di compilazione String contents = myCompilationUnit.getBuffer().getContents(); // Creare un JDOM modificabile myJDOM = new DOMFactory(); myDOMCompilationUnit = myJDOM.createCompilationUnit(contents, "MyClass"); // Esplorare e modificare la struttura dell'unità di compilazione utilizzando // il protocollo dei nodi JDOM. ... // Una volta apportate le modifiche a tutti i nodi // richiamare il codice sorgente dal nodo DOM dell'unità di compilazione. String newContents = myDOMCompilationUnit.getContents(); // Impostare nuovamente questo codice nell'elemento unità di compilazione myCompilationUnit.getBuffer().setContents(newContents); // Salvare il buffer in un file. myCompilationUnit.save();
Questa tecnica potrebbe determinare l'associazione di indicatori di problemi a numeri di riga non corretti dato che gli elementi Java non sono stati aggiornati direttamente.
Il modello di elemento Java non differisce troppo dai metodi e dai campi. La struttura di sintassi astratta utilizzata dal compilatore non è disponibile come API pertanto le tecniche utilizzate dal JDT per analizzare il codice in strutture programmatiche non sono correntemente disponibili come API.
Se il plug-in dell'utente deve essere a conoscenza delle modifiche apportate a elementi Java, è possibile registrare un IElementChangedListener Java con JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
E' possibile specificare ulteriormente il tipo di eventi a cui si è interessati utilizzando addElementChangedListener(IElementChangedListener, int).
Ad esempio, se si è interessati solo al listening di eventi prima che i generatori vengano eseguiti:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.PRE_AUTO_BUILD);
Esistono tre tipi di elementi supportati da JavaCore:
I listener di modifiche degli elementi Java assomigliano concettualmente ai listener di modifiche delle risorse (descritti nella sezione relativa alla traccia delle modifiche delle risorse). Il frammento di codice seguente implementa un reporter di modifiche degli elementi Java che stampa i delta dell'elemento nella console di sistema.
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); } } }
IJavaElementDelta comprende element che era stato modificato e flags che descrive il tipo di modifica apportata. La maggior parte delle volete la struttura delta deriva dal livello del modello Java. I client devono quindi esplorare tale delta utilizzando getAffectedChildren per individuare i progetti modificati.
Il metodo dell'esempio che segue attraversa un delta e restituisce gli elementi aggiunti, rimossi e modificati:
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"); } /* Others flags can also be checked */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Diversi tipi di operazioni possono attivare la notifica della modifica di un elemento Java. Di seguito sono riportati alcuni esempi:
Come per IResourceDelta i delta dell'elemento Java possono essere inseriti in batch mediante un'interfaccia IWorkspaceRunnable. I delta risultanti da diverse operazioni del modello Java eseguite all'interno di un'interfaccia IWorkspaceRunnable vengono riuniti e riportati nello stesso momento.
Ad esempio il seguente frammento attiverà 2 eventi di modifica dell'elemento Java:
// Ottenere il pacchetto IPackageFragment pkg = ...; // Creare 2 unità di compilazione ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Invece l'esempio seguente attiverà 1 evento di modifica dell'elemento Java:
// Ottenere il pacchetto IPackageFragment pkg = ...; // Creare 2 unità di compilazione IWorkspace workspace = ResourcesPlugin.getWorkspace(); workspace.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);