Η πρόσθετη λειτουργία σας μπορεί να χρησιμοποιεί το JDT API για τη δημιουργία κλάσεων ή διεπαφών, την προσθήκη μεθόδων σε υπάρχοντα είδη ή την τροποποίηση των μεθόδων για είδη.
Ο απλούστερος τρόπος για να τροποποιήσετε αντικείμενα Java είναι να χρησιμοποιήσετε το API στοιχείου Java. Για να εργαστείτε με τον αδιαμόρφωτο πρωτογενή κώδικα ενός στοιχείου Java, μπορείτε να χρησιμοποιήσετε πιο γενικές τεχνικές.
Ο πιο εύκολος τρόπος για να δημιουργήσετε με προγραμματισμό μια μονάδα μεταγλώττισης είναι να χρησιμοποιήσετε τη μέθοδο IPackageFragment.createCompilationUnit. Προσδιορίζετε το όνομα και τα περιεχόμενα της μονάδας μεταγλώττισης. Η μονάδα μεταγλώττισης δημιουργείται μέσα στο πακέτο και επιστρέφει η νέα διεπαφή ICompilationUnit.
Μια μονάδα μεταγλώττισης μπορεί να δημιουργηθεί γενικά με δημιουργία ενός πόρου αρχείου, του οποίου η επέκταση είναι ".java" στον κατάλληλο φάκελο που αντιστοιχεί στον κατάλογο του πακέτου. Η χρήση του API γενικού πόρου αποτελεί μια εναλλακτική περίπτωση των εργαλείων Java. Έτσι το μοντέλο Java δεν ενημερώνεται αν δεν ενημερωθούν οι λειτουργίες ακρόασης γενικών αλλαγών πόρου και αν οι λειτουργίες ακρόασης JDT δεν ενημερώσουν το μοντέλο Java με τη νέα μονάδα μεταγλώττισης.
Οι περισσότερες απλές τροποποιήσεις του πρωτογενούς κώδικα Java μπορούν να πραγματοποιηθούν με τη χρήση του API στοιχείου Java.
Για παράδειγμα, μπορείτε να υποβάλλετε ερώτημα για ένα είδος από μια μονάδα μεταγλώττισης. Αφού έχετε τη διεπαφή IType, μπορείτε να χρησιμοποιήσετε πρωτόκολλα όπως είναι το createField, createInitializer, createMethod ή createType για να προσθέσετε μέλη πρωτογενούς κώδικα στο είδος. Ο πρωτογενής κώδικας και οι πληροφορίες σχετικά με τη θέση του μέλους παρέχονται στις μεθόδους αυτές.
Η διεπαφή ISourceManipulation ορίζει κοινούς χειρισμούς πρωτογενούς κώδικα για στοιχεία Java. Συμπεριλαμβάνει μεθόδους για μετονομασία, μετακίνηση, αντιγραφή ή διαγραφή ενός μέλους είδους.
Ο κώδικας μπορεί να τροποποιηθεί με το χειρισμό της μονάδας μεταγλώττισης (και έτσι το υποκείμενο IFile τροποποιείται) ή μπορεί κάποιος να τροποποιήσει ένα αντίγραφο της μονάδας μεταγλώττισης, το οποίο βρίσκεται στη μνήμη και καλείται αντίγραφο εργασίας.
Ένα αντίγραφο εργασίας λαμβάνεται από μια μονάδα μεταγλώττισης με τη χρήση της μεθόδου getWorkingCopy. (Θα πρέπει να σημειωθεί ότι η μονάδα μεταγλώττισης δεν χρειάζεται να υπάρχει στο μοντέλο Java για να δημιουργηθεί ένα αντίγραφο εργασίας.) Όποιος δημιουργεί ένα τέτοιο αντίγραφο εργασίας είναι υπεύθυνος για τη διαγραφή του όταν δεν το χρειάζεται πλέον, με τη χρήση της μεθόδουdiscardWorkingCopy.
Τα αντίγραφα εργασίας τροποποιούν τον ενδιάμεσο χώρο αποθήκευσης που βρίσκεται στη μνήμη. Η μέθοδος getWorkingCopy() δημιουργεί μια προεπιλεγμένη ενδιάμεση μνήμη, αλλά οι πελάτες μπορούν να παρέχουν τη δική τους υλοποίηση ενδιάμεσης μνήμης με τη χρήση της μεθόδου getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor). Οι πελάτες μπορούν να χειρίζονται απευθείας το κείμενο αυτής της ενδιάμεσης μνήμης. Εάν το κάνουν, πρέπει να συγχρονίζουν το αντίγραφο εργασίας με την ενδιάμεση μνήμη, σε τακτά χρονικά διαστήματα, χρησιμοποιώντας μία από τις μεθόδους reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor).
Τέλος, ένα αντίγραφο εργασίας μπορεί να αποθηκευτεί στο δίσκο (αντικαθιστώντας την αρχική μονάδα μεταγλώττισης), με τη χρήση της μεθόδου commitWorkingCopy.
Για παράδειγμα, το παρακάτω τμήμα κώδικα δημιουργεί ένα αντίγραφο εργασίας σε μια μονάδα μεταγλώττισης χρησιμοποιώντας έναν προσαρμοσμένο κάτοχο αντιγράφου εργασίας. Το τμήμα κώδικα τροποποιεί την ενδιάμεση μνήμη, πραγματοποιεί διευθέτηση των αλλαγών, δεσμεύει τις αλλαγές σε ένα δίσκο και τέλος διαγράφει το αντίγραφο εργασίας.
// Get original compilation unit ICompilationUnit originalUnit = ...; // Get working copy owner WorkingCopyOwner owner = ...; // Create working copy ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null); // Modify buffer and reconcile IBuffer buffer = ((IOpenable)workingCopy).getBuffer(); buffer.append("class X {}"); workingCopy.reconcile(NO_AST, false, null, null); // Commit changes workingCopy.commitWorkingCopy(false, null); // Destroy working copy workingCopy.discardWorkingCopy();
Τα αντίγραφα εργασίας μπορούν να είναι κοινόχρηστα σε πολλούς πελάτες, με τη χρήση ενός κατόχου αντιγράφου εργασίας. Αργότερα είναι δυνατή η ανάκτηση ενός αντιγράφου εργασίας με τη χρήση της μεθόδου findWorkingCopy. Έτσι ένα κοινόχρηστο αντίγραφο εργασίας εισάγεται στην αρχική μονάδα μεταγλώττισης και σε έναν κάτοχο αντιγράφου εργασίας.
Το παρακάτω παράδειγμα δείχνει τον τρόπο με τον οποίο ο πελάτης 1 δημιουργεί ένα κοινόχρηστο αντίγραφο εργασίας, ο πελάτης 2 ανακτά αυτό το αντίγραφο εργασίας, ο πελάτης 1 το διαγράφει και ο πελάτης 2 προσπαθώντας να ανακτήσει το κοινόχρηστο αντίγραφο εργασίας αντιλαμβάνεται ότι αυτό δεν υπάρχει πλέον.
// Client 1 & 2: Get original compilation unit ICompilationUnit originalUnit = ...; // Client 1 & 2: Get working copy owner WorkingCopyOwner owner = ...; // Client 1: Create shared working copy ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null); // Client 2: Retrieve shared working copy ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner); // This is the same working copy assert workingCopyForClient1 == workingCopyForClient2; // Client 1: Discard shared working copy workingCopyForClient1.discardWorkingCopy(); // Client 2: Attempt to retrieve shared working copy and find out it's null workingCopyForClient2 = originalUnit.findWorkingCopy(owner); assert workingCopyForClient2 == null;
Μπορείτε να δημιουργήσετε μια κλάση CompilationUnit από την αρχή, χρησιμοποιώντας τις μεθόδους κατασκευής στην κλάση AST. Τα ονόματα των μεθόδων αυτών ξεκινούν με το new.... Παρακάτω δίνεται ένα παράδειγμα το οποίο δημιουργεί μια κλάση HelloWorld.
Το πρώτο τμήμα κώδικα είναι η έξοδος που προκύπτει:
package example;
import java.util.*;
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello" + " world");
}
}
Το παρακάτω τμήμα κώδικα είναι ο αντίστοιχος κώδικας ο οποίος δημιουργεί την έξοδο.
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 χρησιμοποιείται για το διαχωρισμό της πρωτογενούς κώδικα εισόδου σε διακριτικά στοιχεία. Κάθε διακριτικό στοιχείο έχει μια συγκεκριμένη τιμή, η οποία ορίζεται στη διεπαφή ITerminalSymbols. Είναι σχετικά εύκολη η διαδοχική προσέγγιση και η ανάκτηση του σωστού διακριτικού στοιχείου. Συστήνεται επίσης να χρησιμοποιείτε το σαρωτή, αν θέλετε να βρείτε τη θέση της λέξης-κλειδιού super σε μια μέθοδο SuperMethodInvocation.
Ορισμένες τροποποιήσεις πρωτογενούς κώδικα δεν παρέχονται μέσω του API στοιχείου Java. Ένας πιο γενικός τρόπος για να τροποποιήσετε τον πρωτογενή κώδικα (δηλαδή να αλλάξετε τον πρωτογενή κώδικα για υπάρχοντα στοιχεία) είναι να χρησιμοποιήσετε τον αδιαμόρφωτο πρωτογενή κώδικα της μονάδας μεταγλώττισης και να εγγράψετε εκ νέου το API του συνόλου DOM/AST.
Για την επανεγγραφή του DOM/AST, υπάρχουν δύο σύνολα API: η περιγραφική επανεγγραφή και η τροποποιητική επανεγγραφή.
Το περιγραφικό API δεν τροποποιεί την AST αλλά χρησιμοποιεί το API ASTRewrite για να δημιουργήσει τις περιγραφές των τροποποιήσεων.
Η λειτουργία επανεγγραφής AST συλλέγει τις περιγραφές των τροποποιήσεων που πραγματοποιούνται σε κόμβους και μεταφράζει τις περιγραφές αυτές σε τροποποιήσεις κειμένων, οι οποίες στη συνέχεια μπορούν να εφαρμοστούν στον πρωτογενή κώδικα.
// creation of a Document
ICompilationUnit cu = ... ; // content is "public class X {\n}"
String source = cu.getBuffer().getContents();
Document document= new Document(source);
// creation of DOM/AST from a ICompilationUnit
ASTParser parser = ASTParser.newParser(AST.JLS2);
parser.setSource(cu);
CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);
// creation of ASTRewrite
ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());
// description of the change
SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
SimpleName newName = astRoot.getAST().newSimpleName("Y");
rewrite.replace(oldName, newName, null);
// computation of the text edits
TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));
// computation of the new source code
edits.apply(document);
String newSource = document.get();
// update of the compilation unit
cu.getBuffer().setContents(newSource);
Το τροποποιητικό API επιτρέπει την τροποποίηση απευθείας στην AST:
// creation of a Document ICompilationUnit cu = ... ; // content is "public class X {\n}" String source = cu.getBuffer().getContents(); Document document= new Document(source); // creation of DOM/AST from a ICompilationUnit ASTParser parser = ASTParser.newParser(AST.JLS2); parser.setSource(cu); CompilationUnit astRoot = (CompilationUnit) parser.createAST(null); // start record of the modifications astRoot.recordModifications(); // modify the AST TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0) SimpleName newName = astRoot.getAST().newSimpleName("Y"); typeDeclaration.setName(newName); // computation of the text edits TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true)); // computation of the new source code edits.apply(document); String newSource = document.get(); // update of the compilation unit cu.getBuffer().setContents(newSource);
Εάν η πρόσθετη λειτουργία σας πρέπει να ενημερωθεί για τις αλλαγές σε στοιχεία Java, μετά την πραγματοποίησή τους, μπορείτε να καταχωρήσετε μια διεπαφή IElementChangedListener Java με την κλάση JavaCore.
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());
Μπορείτε να γίνετε πιο συγκεκριμένοι και να προσδιορίσετε το είδος των συμβάντων που σας ενδιαφέρει, χρησιμοποιώντας τη μέθοδο addElementChangedListener(IElementChangedListener, int).
Για παράδειγμα, αν ενδιαφέρεστε μόνο για την ακρόαση συμβάντων κατά τη διάρκεια μιας διευθέτησης:
JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);
Υπάρχουν δύο είδη συμβάντων που υποστηρίζονται από την κλάση JavaCore:
Οι λειτουργίες ακρόασης αλλαγών στοιχείων Java είναι παρόμοιες εννοιολογικά με τις λειτουργίες ακρόασης αλλαγών πόρων (περιγράφονται στην ενότητα Παρακολούθηση αλλαγών πόρου). Το παρακάτω τμήμα κώδικα υλοποιεί μια λειτουργία αναφοράς αλλαγών στοιχείων Java που εκτυπώνει τα τροποποιημένα στοιχεία στην κονσόλα του συστήματος.
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 συμπεριλαμβάνει τοστοιχείο το οποίο άλλαξε και τους ενδείκτες που περιγράφουν το είδος της αλλαγής που πραγματοποιήθηκε. Τις περισσότερες φορές, η αφετηρία της διακλάδωσης τροποποιημένων στοιχείων βρίσκεται στο επίπεδο του μοντέλου Java. Οι πελάτες πρέπει να πλοηγηθούν σε αυτά τα τροποποιημένα στοιχεία, χρησιμοποιώντας τη μέθοδο getAffectedChildren για να βρουν τα έργα τα οποία έχουν αλλάξει.
Στη μέθοδο του παρακάτω παραδείγματος, πραγματοποιείται διάσχιση ενός τροποποιημένου στοιχείου και εκτύπωση των στοιχείων που έχουν προστεθεί, των στοιχείων που έχουν αφαιρεθεί και των στοιχείων που έχουν αλλάξει:
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]); } }
Διάφορα είδη λειτουργιών μπορούν να ενεργοποιήσουν μια ενημέρωση αλλαγών σε στοιχεία Java. Δείτε μερικά παραδείγματα:
Ομοίως με την IResourceDelta τα τροποποιημένα στοιχεία των στοιχείων Java μπορούν να ομαδοποιηθούν σε παρτίδες, με τη χρήση μιας διεπαφής IWorkspaceRunnable. Τα τροποποιημένα στοιχεία που προκύπτουν από διάφορες λειτουργίες μοντέλου Java οι οποίες εκτελούνται σε μια διεπαφή IWorkspaceRunnable συγχωνεύονται και αναφέρονται ταυτόχρονα.
Η κλάση JavaCoreπαρέχει μια μέθοδο run για την ομαδοποίηση σε παρτίδες των αλλαγών σε στοιχεία Java.
Για παράδειγμα, το παρακάτω τμήμα κώδικα ενεργοποιεί 2 συμβάντα αλλαγών στοιχείων Java:
// Get package IPackageFragment pkg = ...; // Create 2 compilation units ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null); ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);
Ενώ το παρακάτω τμήμα κώδικα ενεργοποιεί 1 συμβάν αλλαγών στοιχείων Java:
// Get package IPackageFragment pkg = ...; // Create 2 compilation units 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);