Χειρισμός κώδικα Java

Η πρόσθετη λειτουργία σας μπορεί να χρησιμοποιεί το JDT API για τη δημιουργία κλάσεων ή διεπαφών, την προσθήκη μεθόδων σε υπάρχοντα είδη ή την τροποποίηση των μεθόδων για είδη.

Ο απλούστερος τρόπος για να τροποποιήσετε αντικείμενα Java είναι να χρησιμοποιήσετε το API στοιχείου Java. Για να εργαστείτε με τον αδιαμόρφωτο πρωτογενή κώδικα ενός στοιχείου 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;

Τροποποίηση κώδικα με τη χρήση του API DOM/AST

Υπάρχουν τρεις τρόποι για να δημιουργήσετε μια κλάση CompilationUnit. Ο πρώτος είναι να χρησιμοποιήσετε την κλάση ASTParser. Ο δεύτερος είναι να χρησιμοποιήσετε τη μέθοδο ICompilationUnit#reconcile(...). Ο τρίτος είναι να ξεκινήσετε από την αρχή, χρησιμοποιώντας τις μεθόδους κατασκευής στην AST (Διακλάδωση αφηρημένης σύνταξης).

Δημιουργία κλάσης AST από τον υπάρχοντα πρωτογενή κώδικα

Πρέπει να δημιουργηθεί μια χρήση της κλάσης ASTParser με τη μέθοδο ASTParser.newParser(int).

Ο πρωτογενής κώδικας μεταδίδεται στην κλάση ASTParser με μία από τις παρακάτω μεθόδους: Στη συνέχεια, η κλάση AST δημιουργείται με κλήση της μεθόδου createAST(IProgressMonitor).

Το αποτέλεσμα είναι μια κλάση AST με σωστές θέσεις προέλευσης για κάθε κόμβο. Η ανάλυση των δεσμών πρέπει να ζητηθεί πριν από τη δημιουργία της διακλάδωσης με τη μέθοδο setResolveBindings(boolean). Η ανάλυση των δεσμών είναι μια χρονοβόρα διαδικασία και θα πρέπει να πραγματοποιείται μόνο όταν είναι απαραίτητη. Μόλις η διακλάδωση τροποποιηθεί, όλες οι θέσεις και οι δεσμοί χάνονται.

Δημιουργία κλάσης AST με διευθέτηση αντιγράφου εργασίας

Εάν ένα αντίγραφο εργασίας δεν είναι συμβατό (έχει τροποποιηθεί), τότε μπορεί να δημιουργηθεί μια κλάση AST με κλήση της μεθόδου reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor). Για να ζητήσετε τη δημιουργία μιας AST, καλέστε τη μέθοδο reconcile(...) με το AST.JLS2 ως πρώτη παράμετρο.

Οι δεσμοί της υπολογίζονται μόνο αν ο αιτών προβλημάτων είναι ενεργός ή αν ο εντοπισμός προβλημάτων έχει ενεργοποιηθεί. Η ανάλυση των δεσμών είναι μια χρονοβόρα διαδικασία και θα πρέπει να πραγματοποιείται μόνο όταν είναι απαραίτητη. Μόλις η διακλάδωση τροποποιηθεί, όλες οι θέσεις και οι δεσμοί χάνονται.

Από την αρχή

Μπορείτε να δημιουργήσετε μια κλάση 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);

Ανάκτηση επιπλέον θέσεων

Ο κόμβος DOM/AST περιέχει μόνο ένα ζεύγος θέσεων (τη θέση έναρξης και το μήκος του κόμβου). Αυτό δεν είναι πάντα αρκετό. Προκειμένου να ανακτήσετε ενδιάμεσες θέσεις, θα πρέπει να χρησιμοποιήσετε το API της διεπαφής IScanner. Για παράδειγμα, έχουμε μια κλάση InstanceofExpression για την οποία θέλουμε να μάθουμε τις θέσεις του τελεστή instanceof. Για να το πετύχουμε αυτό μπορούμε να γράψουμε την ακόλουθη μέθοδο:
	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

Εάν η πρόσθετη λειτουργία σας πρέπει να ενημερωθεί για τις αλλαγές σε στοιχεία 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);