Java kód kezelése

A bedolgozó a JDT API használatával létrehozhat osztályokat vagy felületeket, létező típusokhoz adhat metódusokat, vagy típusokhoz igazíthat metódusokat.

A Java objektumok megváltoztatásának legegyszerűbb módja a Java elem API használata. Általánosabb eljárások használhatók Java elemek nyers forráskódjának kezelésére.

Kód módosítás Java elemmel

Fordítási egység előállítása

Fordítási egység előállítása programozási eljárással legegyszerűbben az IPackageFragment.createCompilationUnit meghívásával lehetséges. Meg kell adni a fordítási egység nevét és tartalmát. A csomagban létrejön a fordítási egység és az új ICompilationUnit visszatér.

Fordítási egységet általánosan úgy lehet létrehozni, ha létrehoz egy fájl forrást ".java" kiterjesztéssel a csomag könyvtárával megegyező, megfelelő mappában. Az általános erőforrás API használata egy kerülő út a Java eszközökhöz, így a Java modell nem frissül addig, míg az általános erőforrás változás figyelők nem kapnak értesítést és a JDT figelők nem frissítik a Java modellt az új fordítási egységgel.

Fordítási egység módosítása

A Java forrás legegyszerűbb módosításait a Java elem API felülettel végezhetjük el.

Például lekérdezheti a fordítási egység egy típusát. Ha már az IType ismert, akkor forráskód tagokat adhat a típusoz olyan protokollokkal, mint a createField, a createInitializer, vagy a createMethod, vagy a createType. A forráskódot és a tag helyének információit ezekben a metódusokban kell megadni.

Az ISourceManipulation felület a Java elemek általános forráskezelését határozza meg. A felület a típus tagok átnevezéséhez, áthelyezéséhez, másolásához vagy törléséhez biztosít metódusokat.

Munkamásolat

Kódmódosítás a fordítási egység kezelésével végezhető (és így az alapul szolgáló IFile is módosul) vagy a másik megoldás ha a fordítási egység memóriában lévő másolatát módosíthatja; ez utóbbi neve a munkamásolat.

A fordítási egységből nyerhető a munkamásolat a getWorkingCopy metódus használatával. (Nem szükséges, hogy a munkamásolat létrehozásához a fordítási egység létezzen a Java modellben.) Bárki aki létrehoz egy munkamásolatot felelős a megszüntetéséért is, ha arra már nincs szükség. A megszüntetéshez a discardWorkingCopy metódus használható.

A munkamásolatok egy memóriában lévő puffert módosítanak. A getWorkingCopy() metódus létrehoz egy alapértelmezett puffert, de az ügyfél a saját puffer megvalósítását is fenntarthatja a getWorkingCopy(WorkingCopyOwner, IProblemRequestor, IProgressMonitor) metódus használatával. Ennek a puffernek a szövegét az ügyfél közvetlenül kezelheti. Ha így tesz, akkor időről-időre a munkamásolatot szinkronizálni kell a pufferrel a reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor) metódus használatával.

Végül a munkamásolat elmenthető lemezre (az eredeti fordítási egység felülírásával) a commitWorkingCopy metódus használatával.  

Példaként az alábbi kódrészlet létrehoz egy munkamásolatot a fordítási egységről egy egyéni munkamásolat tulajdonos használatával. A részlet módosítja a puffert, összehangolja a változásokat, véglegesíti a változásokat a lemezen, majd megszünteti a munkamásolatot.

    // Eredeti fordítási egység megadása
    ICompilationUnit originalUnit = ...;
    
    // Munkamásolat tulajdonosának megadása
    WorkingCopyOwner owner = ...;
    
    // Munkamásolat létrehozása
    ICompilationUnit workingCopy = originalUnit.getWorkingCopy(owner, null, null);
    
    // Puffer módosítása és egyeztetése
    IBuffer buffer = ((IOpenable)workingCopy).getBuffer();
    buffer.append("class X {}");
    workingCopy.reconcile(NO_AST, false, null, null);
    
    // Módosítások véglegesítése
    workingCopy.commitWorkingCopy(false, null);
    
    // Munkamásolat megsemmisítése
    workingCopy.discardWorkingCopy();

Munkamásolatok megoszthatók számos ügyfél között egy munkamásolat tulajdonos használatával. A munkamásolat későbbiekben visszakereshető a findWorkingCopy metódus használatával. Így a megosztott munkamásolat az eredeti fordítási egységhez és a munkamásolat tulajdonosához is egyaránt rögzítve van.

A következő példában az 1. ügyfél létrehoz egy megosztott munkamásolatot, a 2. ügyfél visszakeresi ezt a munkamásolatot, az 1. ügyfél megszünteti, majd a 2 ügyfél megpróbálja a megosztott munkamásolatot visszakeresni és felismeri, hogy az már nem létezik.

    // 1. & 2. ügyfél: Az eredeti fordítási egység megadása
    ICompilationUnit originalUnit = ...;
    
    // 1. & 2. ügyfél: Munkamásolat tulajdonos megadása
    WorkingCopyOwner owner = ...;
    
    // 1. ügyfél: Megosztott munkamásolat létrehozása
    ICompilationUnit workingCopyForClient1 = originalUnit.getWorkingCopy(owner, null, null);
    
    // 2. ügyfél: Megosztott munkamásolat visszakeresése
    ICompilationUnit workingCopyForClient2 = originalUnit.findWorkingCopy(owner);
     
    // Ez ugyanaz a munkamásolat
    assert workingCopyForClient1 == workingCopyForClient2;
    
    // 1. ügyfél: megosztott munkamásolat megszüntetése
    workingCopyForClient1.discardWorkingCopy();
    
    // 2. ügyfél: kísérlet megosztott munkamásolat visszakeresésére, de felismeri, hogy null.
    workingCopyForClient2 = originalUnit.findWorkingCopy(owner);
    assert workingCopyForClient2 == null;

Kódmódosítás DOM/AST API használatával

A CompilationUnit létrehozására három módszer létezik. Az első lehetőség a ASTParser használata. A második az ICompilationUnit#reconcile(...) használata. A harmadik lehetőség, ha nulláról indulva az AST (Absztrakt szintaxis fa) gyári metódusait használja.

AST létrehozása meglévő forráskódból

Létre kell hozni egy ASTParser példányt a ASTParser.newParser(int) segítségével.

Forráskódot az ASTParser objektumhoz hozzáadni a következő módszerek egyikével lehetséges: Ezután a createAST(IProgressMonitor) meghívásával az AST létrejön.

A folyamat eredményeként egy AST jön létre, minden csomópontnál helyes forráspozícióval. Mielőtt létrehozza a fát, a setResolveBindings(boolean) metódussal kérni kell a kötések felbontását. A kötések feloldása költséges művelet, ezért végrehajtása csak szükség esetén javasolt. A fa módosításával a pozíciók és a kötések elvesznek.

AST létrehozása munkamásolat egyeztetéssel

Ha egy munkamásolat nem konzisztens (módosult), akkor a reconcile(int, boolean, WorkingCopyOwner, IProgressMonitor) meghívásával AST hozható létre. AST létrehozás kérelmezéséhez hívja meg a reconcile(...) metódust, úgy hogy első paramétere az AST.JLS2 legyen.

A rendszer csak akkor számítja ki a kötéseket, ha a probléma kérelmező aktív, vagy ha a problémafelismerés kényszerített. A kötések feloldása költséges művelet, ezért végrehajtása csak szükség esetén javasolt. A fa módosításával a pozíciók és a kötések elvesznek.

Nulláról

Lehetséges CompilationUnit létrehozása nulláról indulva az AST gyári metódusaival. Ezek a metódusnevek a new... taggal kezdődnek. A következő példa egy HelloWorld osztályt hoz létre.

A követező részlet az előállított kimenet:

	package example;
	import java.util.*;
	public class HelloWorld {
		public static void main(String[] args) {
			System.out.println("Hello" + " world");
		}
	}

A következő részlet a kimenetet előállító, vonatkozó kód.

		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);

Extra pozíciók visszakeresése

A DOM/AST csomópont csak egy pár pozíciót tartalmaz (a csomópont kezdeti pozícióját és hosszát). Ez nem minden esetben elégséges. Köztes pozíciók visszakereséséhez az IScanner API használható. Például ha egy InstanceofExpression instanceof operátorának pozícióit szeretné megtudni, akkor a cl elérése érdekében a következő metódust hozhatja létre:
	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;
	}
Az IScanner metódus a bemeneti forrást felosztja jelsorokká. Minden jelsornak adott értéke van, amint az a ITerminalSymbols felületben meg van határozva. A megfelelő jelsor iterálása és visszakeresése nagyon egyszerű. A super kulcsszó megtalálásához a SuperMethodInvocation kifejezésben a kereső ajánlott.

Forráskód módosítás

Bizonyos forráskód módosítások nem biztosítottak a Java elem API felületén keresztül. A forráskód szerkesztésének ennél általánosabb módszere (úgy mint a már meglévő elemek forráskódjának megváltoztatása) a fordítási egység nyers forráskódjának és a DOM/AST újraíró API felületének használata.

A DOM/AST újraíráshoz két készlet API áll rendelkezésre: a leíró újraírás és a módosító újraírás.

A leíró API nem módosítja az AST fát, hanem az ASTRewrite API felületet használja a módosítások leírásának elkészítéséhez. Az AST újraíró összegyűjti a csomópont módosítások leírásait és lefordítja olyan szövegszerkesztésekre, amely azután alkalmazható az eredeti forrásra.

   // dokumentum létrehozása
   ICompilationUnit cu = ... ; // tartalma: "public class X {\n}"
   String source = cu.getBuffer().getContents();
   Document document= new Document(source);

   // DOM/AST létrehozása ICompilationUnit objektumból
   ASTParser parser = ASTParser.newParser(AST.JLS2);
   parser.setSource(cu);
   CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);

   // ASTRewrite létrehozása
   ASTRewrite rewrite = new ASTRewrite(astRoot.getAST());

   // a változás leírása
   SimpleName oldName = ((TypeDeclaration)astRoot.types().get(0)).getName();
   SimpleName newName = astRoot.getAST().newSimpleName("Y");
   rewrite.replace(oldName, newName, null);

   // szövegszerkesztések kiszámítása
   TextEdit edits = rewrite.rewriteAST(document, cu.getJavaProject().getOptions(true));

   // új forráskód kiszámítása
   edits.apply(document);
   String newSource = document.get();

   // fordítási egység frissítése
   cu.getBuffer().setContents(newSource);

A módosító API lehetővé teszi az AST közvetlen változtatását:

   // dokumentum létrehozása
   ICompilationUnit cu = ... ; // tartalma: "public class X {\n}"
   String source = cu.getBuffer().getContents();
   Document document= new Document(source);

   // DOM/AST létrehozása ICompilationUnit objektumból
   ASTParser parser = ASTParser.newParser(AST.JLS2);
   parser.setSource(cu);
   CompilationUnit astRoot = (CompilationUnit) parser.createAST(null);

   // módosítások rögzítésének indítása
   astRoot.recordModifications();

   // AST módosítása
   TypeDeclaration typeDeclaration = (TypeDeclaration)astRoot.types().get(0)
   SimpleName newName = astRoot.getAST().newSimpleName("Y");
   typeDeclaration.setName(newName);

   // szövegszerkesztések kiszámítása
   TextEdit edits = astRoot.rewrite(document, cu.getJavaProject().getOptions(true));

   // új forráskód kiszámítása
   edits.apply(document);
   String newSource = document.get();

   // fordítási egység frissítése
   cu.getBuffer().setContents(newSource);

Válasz Java elemek módosításaira

Ha a bedolgozónak tudnia kell Java elemek megváltozásáról, akkor bejegyezhet egy Java IElementChangedListener felületet a JavaCore osztállyal.

   JavaCore.addElementChangedListener(new MyJavaElementChangeReporter());

Ha meg szeretné határozni, hogy milyen típusú események figyelése szükséges, akkor használja az addElementChangedListener(IElementChangedListener, int) osztályt.

Például, ha csak egyeztetési műveletek eseményeinek figyelése érdekli:

   JavaCore.addElementChangedListener(new MyJavaElementChangeReporter(), ElementChangedEvent.POST_RECONCILE);

A JavaCore kétféle eseményt támogat:

A Java elem változás figyelők lényegüket tekintve hasonlóak a forrásváltozás figyelőkhöz (leírás: forrásváltozások nyomkövetése). A következő programrészlet egy Java elem változásjelentőt valósít meg, amely a rendszerkonzolra nyomtatja az elemkülönbségeket.

   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);
         }
      }
   }

Az IJavaElementDelta része a megváltozott elem, és a lezajlott változás jellemzőket leíró jelzők. A különbségfa kiindulási pontja leggyakrabban a Java modell szint. Az ügyfeleknek ezt a különbséget kell navigálniuk a getAffectedChildren használatával, hogy megtudják milyen projektek változtak.

A következő példametódus átvizsgál egy különbséget, majd kinyomtatja a hozzáadott, eltávolított, módosított elemeket:

    void traverseAndPrint(IJavaElementDelta delta) {
        switch (delta.getKind()) {
            case IJavaElementDelta.ADDED:
                System.out.println(delta.getElement() + " hozzá lett adva");
               break;
            case IJavaElementDelta.REMOVED:
                System.out.println(delta.getElement() + " eltávolításra
került");
               break;
            case IJavaElementDelta.CHANGED:
                System.out.println(delta.getElement() + " módosult");
                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");
                }
                /* Más jelzőket is ellenőrízhet */
               break;
        }
        IJavaElementDelta[] children = delta.getAffectedChildren();
        for (int i = 0; i < children.length; i++) {
            traverseAndPrint(children[i]);
        }
    }

Több fajta művelet indíthat el Java elem változási értesítést. Álljon itt néhány példa:

Az IResourceDelta felülethez hasonlóan a Java elem különbségek kötegelhetőek az IWorkspaceRunnable használatával. Számos Java modell művelet eredményeként kapott, az IWorkspaceRunnable felületen futó különbségek összevonásra kerülnek és egy jelentésben jellenek meg.  

A JavaCore biztosít futási metódust Java elem változások kötegelésére.

Például a következő kódtöredék 2 Java elem változási eseményt fog aktiválni:

    // Csomag megadása
    IPackageFragment pkg = ...;
    
    // két fordítási egység létrehozása
 	            ICompilationUnit unitA = pkg.createCompilationUnit("A.java", "public class A {}", false, null);
 	            ICompilationUnit unitB = pkg.createCompilationUnit("B.java", "public class B {}", false, null);

Míg a következő kódtöredék 1 Java elem változási eseményt fog aktiválni:

    // Csomag megadása
    IPackageFragment pkg = ...;
    
    // két fordítási egység létrehozása
    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);