O plug-in pode utilizar a API JDT para criar classes ou interfaces, adicionar métodos a tipos existentes ou alterar os métodos para tipos.
A forma mais fácil de alterar objectos Java é utilizar a API de elemento Java. Podem ser utilizadas mais técnicas gerais para trabalhar com código fonte em bruto para um elemento Java.
A forma mais fácil para gerar de forma programada uma unidade de compilação é utilizar IPackageFragment.createCompilationUnit. Indique o nome e conteúdo da unidade de compilação. A unidade de compilação é criada dentro do pacote e a nova ICompilationUnit é devolvida.
De forma geral, pode-se criar a unidade de compilação com um recursos de ficheiro cuja extensão seja ".java" na pasta adequada que corresponde ao directório do pacote. Com o recurso genérico, a API é uma porta das traseiras para utilizar as ferramentas Java, logo, o modelo Java não é actualizado até que o recurso genérico seja alterado e os ouvintes sejam notificados; os ouvintes JDT actualizam o modelo Java com a nova unidade de compilação.
As modificações mais simples de código Java podem ser feitas utilizando uma API de elemento Java.
Por exemplo, pode consultar um tipo de uma unidade de compilação. Assim que obtiver o IType, pode utilizar protocolos como createField, createInitializer, createMethod ou createType para adicionar membros de código fonte ao tipo. Estes métodos fornecem o código fonte e as informações sobre a localização do membro.
A interface ISourceManipulation define manipulações de fontes comuns em elementos Java. Isto inclui métodos para Mudar de nome, Copiar ou Eliminar um membro de um tipo.
O Código pode ser modificado, manipulando a unidade de compilação (e o IFile subjacente é modificado) ou pode modificar-se uma cópia na memória da unidade de compilação denominada cópia de trabalho.
A cópia de trabalho é obtida de uma unidade de compilação utilizando o método getWorkingCopy. (Tenha em atenção que a unidade de compilação não tem de existir no modelo Java para que a cópia de trabalho seja criada.) Quem criar essa cópia de trabalho fica responsável pela sua eliminação, quando deixar de ser necessária, utilizando o método discardWorkingCopy.
As cópias de trabalho modificam a memória tampão. O método getWorkingCopy() cria uma memória tampão predefinida, mas os clientes podem facultar a implementação da sua própria memória tampão com o método getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Os clientes podem manipular directamente o texto desta memória tampão. Se o fizerem, têm de sincronizar a cópia de trabalho com a memória tampão regularmente, utilizando o método reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
Finalmente, é possível guardar uma cópia de trabalho no disco (substituindo a unidade de compilação inicial) com o método commitWorkingCopy.
Por exemplo, o seguinte fragmento de código cria uma cópia de trabalho numa unidade de compilação com um proprietário personalizado de uma cópia de trabalho. O fragmento modifica a memória tampão, reconcilia as alterações, consolida as alterações no disco e finalmente rejeita a cópia de trabalho.
// Obter unidade de compilação original ICompilationUnit originalUnit = ...; // Obter proprietário da cópia de trabalho WorkingCopyOwner owner = ...; // Criar cópia de trabalho ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modificar memória tampão e reconciliar IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Consolidar alterações workingCopy.commitWorkingCopy(false, null); // Destrui cópia de trabalho workingCopy.discardWorkingCopy();
As cópias de trabalho podem ainda ser partilhadas por vários clientes utilizando o proprietária da cópia de trabalho. A cópia de trabalho pode ser obtida mais tarde com o método findWorkingCopy. Uma cópia de trabalho partilhada é, então, protegida por chave na unidade de compilação original e no proprietário da cópia de trabalho.
O seguinte fragmento mostra como o cliente 1 cria uma cópia de trabalho partilhada, o cliente 2 obtém essa cópia de trabalho, o cliente 1 rejeita a cópia de trabalho e o cliente 2 ao tentar obter a cópia de trabalho partilhada repara que esta já não existe:
// Cliente 1 & 2: Obter unidade de compilação original ICompilationUnit originalUnit = ...; // Cliente 1 & 2: Obter proprietário da cópia de trabalho WorkingCopyOwner owner = ...; // Cliente 1: Criar cópia de trabalho partilhada ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Cliente 2: Obter cópia de trabalho partilhada ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // Esta é a mesma cópia de trabalho assert workingCopyForClient1 == workingCopyForClient2; // Cliente 1: Rejeitar cópia de trabalho partilhada workingCopyForClient1.discardWorkingCopy(); // Cliente 2: Tentar obter cópia de trabalho partilhada e descobri que é null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
É possível criar uma CompilationUnit desde o princípio com os métodos factory numa AST. Os nomes destes métodos começam por new.... O seguinte é um exemplo que cria uma classe HelloWorld.
O primeiro fragmento é a saída gerada:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
O fragmento seguinte é o código correspondente que gera a saída.
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; }O IScanner é utilizado para dividir a fonte de entrada em sinais. Cada sinal tem um valor específico que está definido na interface ITerminalSymbols. É relativamente simples iterar e obter o sinal correcto. Recomenda-se ainda o uso do explorador se quiser encontrar a posição da palavra-chave super numa SuperMethodInvocation.
Algumas modificações de código fonte não são fornecidas pela API de elemento Java. A forma mais geral de editar o código fonte (como seja mudar o código fonte para os elementos existentes) é utilizar o código fonte em bruto da unidade de compilação e reescrever a API de DOM/AST.
Para reescrever DOM/AST, existem dois conjuntos de APIs: a reescrita descritiva e a reescrita modificadora.
A API descritiva não modifica a AST mas utiliza a API ASTRewrite para gerar as descrições de modificações.
Esta recolhe as descrições de modificações a nós e traduz estas descrições em edições de texto que podem ser aplicadas ao código original.
// criação de um Documento
ICompilationUnit cu = ... ; // content is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// criação de DOM/AST de uma ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// criação de ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// descrição da alteração
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// cálculo das edições de texto
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// cálculo do novo código fonte
edits.apply(document);
String newSource = document.get();
// actualização da unidade de compilação
cu.getBuffer().setContents(newSource);
A API modificador permite modificar directamente a AST:
// criação de um Documento ICompilationUnit cu = ... ; // content is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // criação de DOM/AST de uma ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // iniciar registo de modificações astRoot.recordModifications(); // modificar a AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // cálculo das edições de texto TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // cálculo do novo código fonte edits.apply(document); String newSource = document.get(); // actualização da unidade de compilação cu.getBuffer().setContents(newSource);
Se o plug-in tiver de ser informado sobre as alterações a elementos Java depois de estas ocorrerem, pode registar um IElementChangedListener Java no JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Pode ser mais específico e indicar o tipo de eventos que interessam com o addElementChangedListener(IElementChangedListener, int).
Por exemplo, se só lhe interessar saber dos eventos durante uma operação de reconciliação:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
Existem duas espécies de eventos suportados pelo JavaCore:
Os ouvintes de alteração a elementos Java têm um conceito semelhante aos ouvintes de alteração em recursos (descritos em rastrear alterações a recursos). O fragmento seguinte implementa um repórter de alteração a elemento Java que imprime os deltas do elemento na consola do 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); } } }
O IJavaElementDelta inclui o elemento que foi alterado e os sinalizadores que descrevem a espécie de alteração que ocorreu. A maioria das vezes, a árvore de deltas está enraizada no nível de Modelo Java. Os cliente têm, assim, de navegar neste delta com getAffectedChildren para encontrar os projectos que foram alterados.
O método exemplo seguinte atravessa um delta e imprime os elementos adicionados, removidos e alterados:
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"); } /* Podem verificar-se outros sinalizadores */ break; } IJavaElementDelta[] children = delta.getAffectedChildren(); for (int i = 0; i < children.length; i++) { traverseAndPrint(children[i]); } }
Várias espécies de operações podem activar a notificação de alteração a elemento Java. Apresentam-se alguns exemplos:
À semelhança de IResourceDelta, os deltas de elementos Java podem ser agrupados utilizando um IWorkspaceRunnable. Os deltas oriundos de várias operações de Modelo Java que são executados num IWorkspaceRunnable são fundidos e comunicados ao mesmo tempo.
O JavaCore fornece um método run para agrupar alterações de elementos Java.
Por exemplo, o fragmento de código seguinte vai activar dois eventos de alteração a elementos Java:
// Obter pacote IPackageFragment pkg = ...; // Criar duas unidades de compilação ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Enquanto que o fragmento de código seguinte vai activar um evento de alteração a elementos Java:
// Obter pacote IPackageFragment pkg = ...; // Criar duas unidades de compilação 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);