Annotation einfügen


Problem

Eine Annotation soll an einer bestimmten Stelle in das Programm eingefügt werden, z.B. durch ein Refactoring.

Lösung aus Declared Type Generalization Checker

Die Aufgabe ist, ausgehend von einer Markierung (vom Typ IMarker) im Quelltext vor diese Markierung eine Annotation (@Sic) zu setzen. Gekapselt wird diese Funktionalität von der Klasse AnnotationManager. Diese Klasse implementiert zunächst drei öffentliche static Methoden: hasSicAnnotation prüft ob ein Knoten des Abstract-Syntax-Tree mit der @Sic-Annotation versehen ist:

1: public static boolean hasSicAnnotation(ASTNode node) {
2:     IAnnotationBinding annotationBindings[] = null;
3:	
4:     if (node instanceof VariableDeclaration) {
5:         VariableDeclaration declaration = (VariableDeclaration) node;
6:         annotationBindings = declaration.resolveBinding().getAnnotations();
7:     }
8:     if (node instanceof MethodDeclaration) {
9:         MethodDeclaration declaration = (MethodDeclaration) node;
10:        annotationBindings = declaration.resolveBinding().getAnnotations();
11:    }
12:
13:    for (int i = 0; i < annotationBindings.length; i++) {
14:        String annotationName = annotationBindings[i].getName();
15:        if (annotationName.equals(SIC_ANNOTATION)) {
16:            return true;
17:        }
18:    }
19:	
20:    return false;
21: }	

Listing: Prüfung ob ein AST-Knoten bereits annotatiert ist

isProjectAnnotatable prüft ob in dem Projekt mit Annotationen gearbeitet werden kann (>= Java 1.5):

1: public static boolean isProjectAnnotatable(IJavaProject project) {
2:     String sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true);
3:     if (sourceLevel.equals(JavaCore.VERSION_1_1) ||
4:           sourceLevel.equals(JavaCore.VERSION_1_2) ||
5:           sourceLevel.equals(JavaCore.VERSION_1_3) ||
6:           sourceLevel.equals(JavaCore.VERSION_1_4)) {
7:         return false;
8:     }
9:     else {
10:        return true;
11:    }
12: }

Listing: Prüfung ob Projekt annotierbar

annotateDeclaration setzt die Annotation vor eine Markierung im Quelltext:

1: public static void annotateDeclaration(IMarker marker) throws CoreException {
2:     IJavaProject javaProject = getJavaProjectFromMarker(marker);
3:     ICompilationUnit unit = getCompilationUnitFromMarker(marker);
4:	
5:     annotateDeclaration(marker, unit);
6: 		 
7:     if (! hasSicAnnotationType(javaProject)) {
8:         createSicAnnotationType(javaProject);
9:     }
10:    if (! hasSicAnnotationImport(unit)) {
11:        insertSicAnnotationImport(unit);
12:    }
13: }

Listing: Öffentliche Methode um die Annotation zu setzen.

Um die Annotation zu setzen wird annotateDeclaration mit der Markierung als Parameter aufgerufen. Intern benötigt die Methode eine Referenz auf ein Objekt vom Typ IJavaProject und ICompilationUnit, die beide aus der Markierung gewonnen werden können. Hierfür werden die privaten Hilfsmethoden getJavaProjectFromMarker und getCompilationUnitFromMarker verwendet:

1: private static IJavaProject getJavaProjectFromMarker(IMarker marker) throws CoreException {
2:     IProject project = marker.getResource().getProject();
3:     IJavaProject javaProject = JavaCore.create(project);
4:     if (javaProject == null) {
5:         throw new CoreException (new Status (Status.ERROR, 
6: 						DeclaredTypeGeneralizationCheckerPlugin.getDefault().getBundle().getSymbolicName(),
7:   						Status.ERROR,
8: 						Messages.getString("Builder.ErrorJavaProject"),
9: 					        null));
10:    }
11:    return javaProject;
12: }
13:	
14: private static ICompilationUnit getCompilationUnitFromMarker(IMarker marker) throws CoreException {
15:    IJavaElement javaElement = JavaCore.create(marker.getResource());
16:    ICompilationUnit unit = null;
17:    if (javaElement instanceof ICompilationUnit) {
18:        unit = (ICompilationUnit) javaElement;
19:    }
20:    if (unit == null) {
21:        throw new CoreException (new Status (Status.ERROR, 								  
22:					        DeclaredTypeGeneralizationCheckerPlugin.getDefault().getBundle().getSymbolicName(),
23:                                             Status.ERROR,
24:						Messages.getString("Builder.ErrorJavaProject"),
25:                                             null));
26:    }
27:    return unit;
28: }

Listing: Hilfsmethoden

Nachdem die benötigten Objekte erzeugt wurden, ruft annotateDeclaration (ein Parameter), die private Methode annotateDeclaration (zwei Parameter) auf. Diese erzeugt den Abstract-Syntax-Tree für die ICompilationUnit auf der die Markierung sitzt und traversiert den Baum mit Hilfe eines Visitors um den AST-Knoten der Deklaration zu erhalten die annotiert werden soll:

1: private static void annotateDeclaration(IMarker marker, ICompilationUnit sourceUnit) throws CoreException {
2:     ASTParser parser = ASTParser.newParser(AST.JLS3);
3:     parser.setKind(ASTParser.K_COMPILATION_UNIT);
4:     parser.setSource(sourceUnit);	
5:     CompilationUnit unit = (CompilationUnit) parser.createAST(null);
6:     unit.accept(new DeclarationToAnnotateVisitor(marker.getAttribute(ProblemMarker.TYPE_START, 0), unit, sourceUnit));
7: }

Listing: Erzeugen des AST und Traversierung mit dem Visitor

Der Visitor selbst ist als innere Klasse definiert und enthält visit-Methoden für die annotierbaren Deklarationselemente:

1: private static class DeclarationToAnnotateVisitor extends ASTVisitor {
2:     private int startPostition;
3:     private boolean declarationFound = false;
4:     private CompilationUnit unit = null;
5:     private ICompilationUnit sourceUnit = null;
6: 		
7:     public DeclarationToAnnotateVisitor(int startPosition, CompilationUnit unit, ICompilationUnit sourceUnit) {
8:         this.startPostition = startPosition;
9:         this.unit = unit;
10:        this.sourceUnit = sourceUnit;
11:    }
12:		
13:    public boolean visit(FieldDeclaration node) {
14:        if (! declarationFound) {
15:           if (startPostition == node.getType().getStartPosition()) {
16:               AnnotationManager.annotateDeclaration(node, unit, sourceUnit);
17:               declarationFound = true;
18:               return false;
19:           }
20:           else {
21:               return true;
22:           }
23:       }
24:       else {
25:           return false;
26:       }			
27:   }
28:		
29:   public boolean visit(MethodDeclaration node) {
30:       if (! declarationFound) {
31:           if (! node.isConstructor()) {
32:               if (startPostition == node.getReturnType2().getStartPosition()) {
33:                   AnnotationManager.annotateDeclaration(node, unit, sourceUnit);
34:                   declarationFound = true;
36:                   return false;
37:               }
38:           }	
39:				
40:           List methodParameters = node.parameters();
41:           Iterator methodParameterIterator = methodParameters.iterator();
42:           while (methodParameterIterator.hasNext()) {
43:               SingleVariableDeclaration methodParameter = (SingleVariableDeclaration) methodParameterIterator.next();
44:               if (startPostition == methodParameter.getType().getStartPosition()) {
45:                   AnnotationManager.annotateDeclaration(methodParameter, unit, sourceUnit);
46:                   declarationFound = true;
47:                   return false;
48:               }
49:           }
50:           return true;
51:       }
52:       else {
53:           return false;
54:       }	
55:   }
56:	
57:   public boolean visit(VariableDeclarationStatement node) {
58:       if (! declarationFound) {
59:           if (startPostition == node.getType().getStartPosition()) {
60:               AnnotationManager.annotateDeclaration(node, unit, sourceUnit);
61:               declarationFound = true;
62:               return false;
63:           }
64:           else {
65:               return true;
66:           }
67:       }
68:       else {
69:           return false;
70:       }	
71:   }
72:}

Listing: Visitor als innere Klasse

In der 'visit'-Methode des zu annotierenden Deklarationselements wird die Annotation letztlich durch den Aufruf der Methode 'annotateDeclaration' (3 Parameter) eingefügt:

1: private static void annotateDeclaration(ASTNode node, CompilationUnit unit, ICompilationUnit sourceUnit) {
2:     Document sourceDocument = null;
3:     try {
4:         sourceDocument = new Document(sourceUnit.getSource());
5:     } catch (JavaModelException e) {
6:         IStatus status = new Status (Status.ERROR, 
7:                                      DeclaredTypeGeneralizationCheckerPlugin.getDefault().getBundle().getSymbolicName(),
8:                                      Status.ERROR, Messages.getString("Annotation.Error"), e);
9:         DeclaredTypeGeneralizationCheckerPlugin.getDefault().getLog().log(status);
10:    }
11:		
12:    unit.recordModifications();
13:
14:    AST ast = unit.getAST();
15:    MarkerAnnotation sicAnnotation = ast.newMarkerAnnotation();
16:    sicAnnotation.setTypeName(ast.newSimpleName(SIC_ANNOTATION));		
17:    addAnnotation(node, sicAnnotation);
18:	
19:    try {
20:        Map editorOptions = sourceUnit.getJavaProject().getOptions(true);
21:        editorOptions.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_AFTER_ANNOTATION, JavaCore.DO_NOT_INSERT);
22:			
23:        TextEdit edits = unit.rewrite(sourceDocument, editorOptions);
24:        edits.apply(sourceDocument);
25:			
26:        String newSource = sourceDocument.get();
27:        sourceUnit.getBuffer().setContents(newSource);
28:        sourceUnit.save(null, false);
29:			
30:        IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
31:        editor.doSave(null);		
32:    } catch (Throwable t) {
33:        IStatus status = new Status (Status.ERROR, 
34:                                     DeclaredTypeGeneralizationCheckerPlugin.getDefault().getBundle().getSymbolicName(),
35:                                     Status.ERROR, Messages.getString("Annotation.Error"), t);
36:
37:        DeclaredTypeGeneralizationCheckerPlugin.getDefault().getLog().log(status);
38:    }
39:}

Listing: Einfügen der Annotation in den Editor

Verwendet wird noch die Hilfsmethode 'addAnnotation', die die die Annotation als Modifier des Deklarationselements einträgt:

1: private static void addAnnotation(ASTNode node, IExtendedModifier annotation) {
2:     VariableDeclarationStatement localVariable = null;
3:     if (node instanceof VariableDeclarationStatement) {
4:         localVariable = (VariableDeclarationStatement) node;
5:         localVariable.modifiers().add(annotation);
6:     }
7:     SingleVariableDeclaration variable = null;
8:     if (node instanceof SingleVariableDeclaration) {
9:         variable = (SingleVariableDeclaration) node;
10:        variable.modifiers().add(annotation);
11:    }
12:    MethodDeclaration method = null;
13:    if (node instanceof MethodDeclaration) {
14:        method = (MethodDeclaration) node;
15:        method.modifiers().add(annotation);
16:    }
17:    FieldDeclaration field = null;
18:    if (node instanceof FieldDeclaration) {
19:        field = (FieldDeclaration) node;
20:        field.modifiers().add(annotation);
21:    }
22:}

Listing: Hinzufügen des Modifiers

Die Methode annotateDeclaration (1 Parameter) benutzt noch vier Hilfsmethoden, die dazu dienen, zu prüfen ob im Projekt die Typdeklaration der Annotation enthalten ist und ob die Importdeklaration in der zu annotierenden Klasse vorhanden ist. Darüber hinaus werden die entsprechenden Deklarationen ggf. gleich eingefügt:

1: private static boolean hasSicAnnotationType(IJavaProject javaProject) throws CoreException {
2:     IPackageFragment packages[] = null;
3:     try {
4:         packages = javaProject.getPackageFragments();
5:     } catch (JavaModelException jme) {
6:         throw new CoreException (new Status (Status.ERROR, 
7:                                              DeclaredTypeGeneralizationCheckerPlugin.getDefault().getBundle().getSymbolicName(),
8:                                              Status.ERROR, Messages.getString("Builder.ErrorJavaFragments"),
9:                                              jme));
10:    }
11:		
12:    for (int i = 0; i < packages.length; i++) {
13:        String packageName = packages[i].getElementName();
14:        if (packageName.equals(SIC_ANNOTATION_PACKAGE)) {
15:            ICompilationUnit unit = packages[i].getCompilationUnit(SIC_ANNOTATION_FILE);
16:            if (unit == null) {
17:                return false;
18:            }
19:            else {
20:                return true;
21:            }
22:        }
23:    }
24:    return false;
25:}
26:	
27:private static void createSicAnnotationType(IJavaProject javaProject) throws CoreException {
28:    IPackageFragmentRoot srcPackages[] = javaProject.getPackageFragmentRoots();
29:    IPackageFragment newPackage = srcPackages[0].createPackageFragment(SIC_ANNOTATION_PACKAGE, true, null);
30:    newPackage.createCompilationUnit(SIC_ANNOTATION_FILE, SIC_ANNOTATION_DECLARATION, true, null);
31:}
32:	
33:private static boolean hasSicAnnotationImport(ICompilationUnit unit) throws CoreException {
34:    IImportDeclaration importDecalaration = unit.getImport(SIC_ANNOTATION_PACKAGE +"."+ SIC_ANNOTATION);
35:    if (importDecalaration.exists()) {
36:        return true;
37:    }
38:    else {		
39:        return false;
40:    }
41:}
42:	
43:private static void insertSicAnnotationImport(ICompilationUnit unit) throws CoreException {
44:    unit.createImport(SIC_ANNOTATION_PACKAGE +"."+ SIC_ANNOTATION, null, null);
45:    unit.save(null, true);
46:		
47:    IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
48:    editor.doSave(null);
49:}

Deklariert ist die Annotation selbst wie folgt:

1: private static final String SIC_ANNOTATION = "Sic"; 
2: private static final String SIC_ANNOTATION_PACKAGE = "org.intoJ.dtgc.annotation";
3: private static final String SIC_ANNOTATION_FILE = "Sic.java";
4: private static final String SIC_ANNOTATION_DECLARATION = "package org.intoJ.dtgc.annotation;\n\n/*DO NOT CHANGE THIS FILE, IT IS NEEDED BY THE DECLARED TYPE GENERALIZATION CHECKER PLUG-IN.*/\n\npublic @interface Sic {}";

Vorkommen

siehe auch

Zuletzt geändert am 15. Juli 2010 um 15:35