DevFaqCodeCompletionJavaAware DE

Code Completion global im Java Quelltext

Contributed By; Aljoscha Rittner | Josh


Ursprünglicher Blog-Eintrag: BeanDev: Code Completion global im Java Quelltext

Problemstellung


Ein Code Completion-Modul zu erstellen ist eigentlich relativ trivial. Erster Startpunkt ist ein Tutorial für NetBeans 6.5. 1

Das Problem bei der API zu Code Completion ist, dass man im Prinzip nur mit Swing-Klassen arbeitet - man hat also nur puren Text und kann erstmal gar nicht semantisch mit dem Quelltext arbeiten.

Da gibt es im Prinzip zwei Seiten, wo man Semantik vermisst. Zum einen bei der passenden Darstellung der Code Completion List (d.h. nur das darstellen, was zur Eingabeposition passt) und zum anderen kann man schwer globale Modifikationen im Quelltext durchführen (zum Beispiel einen passenden Import hinzufügen, wenn der Anwender einen Eintrag aus der Code Completion Liste ausgewählt hat).

Die Problematik des passenden Scopes hat Geertjan in seinem Blog 2 schon angesprochen. Er erweitert das obige Tutorial mit einer CaretAwareJavaSourceTaskFactory. Das ist ein eigenständiger Service, der etwas locker und sehr statisch aber effizient den CodeCompletionProvider steuert.

Nun kann man also semantisch steuern, welche Code Completion Einträge in der Liste dargestellt werden sollen. Das erspart dem Anwender unnötige Einträge, die gar nicht an die aktuelle Stelle des Quelltextes eingefügt werden können (weil sonst die Syntax hinüber ist).

Was ist aber mit einer globalen Änderung des Quelltextes?

Lösung


Die Implementierende Klasse von CompletionItem

  public void defaultAction(JTextComponent jTextComponent);

übergibt nur eine JTextComponent. Daraus holt man sich das Document und kann dann bestenfalls (ohne größeren Aufwand) an der Caret-Position den Text einfügen und ggf. noch ein paar Zeichen ersetzen.

Für Modifikationen außerhalb dieses Bereiches bietet die Completion API keine Hilfe.

Dafür gibt es aber die Java Source Infrastructure die aus mehreren Grundpaketen und Modulen aufgebaut ist. Verwendet wird javax.lang.model.*, dazu gesellt sich org.netbeans.api.java.source.* und wird letztendlich von com.sun.source.* unterstützt. Eine Code-Snippet-Sammlung zur Java Infrastructure findet sich auf dieser Wiki-Seite 3.

Wie bringt man das alles zusammen?

Zentraler Einstiegspunkt ist für diesen Fall die JavaSource-Klasse. Sie bietet eine statische Methode, um aus einem Document eine JavaSource-Instance zu generieren:

public void defaultAction(JTextComponent jTextComponent) {
  Document doc = jTextComponent.getDocument();
  JavaSource source = JavaSource.forDocument (doc);
  fixImport (source, "java.awt.Color");
}

Und mit dem JavaSource-Objekt kann man Tasks starten, um am Quelltext Modifikationen durchzuführen:

  private void fixImport (final JavaSource source, final String importDeclaration) {
    new Thread (new Runnable() {
      public void run() {
        try {
          ModificationResult result = source.runModificationTask(new Task<WorkingCopy>() {
            public void run(WorkingCopy workingCopy) throws Exception {
              workingCopy.toPhase(Phase.ELEMENTS_RESOLVED); 

              CompilationUnitTree cut = workingCopy.getCompilationUnit();
              TreeMaker make = workingCopy.getTreeMaker();
              List<? extends ImportTree> imports = cut.getImports();
              for (ImportTree it : imports) {
                if (it.getQualifiedIdentifier() instanceof MemberSelectTree) {
                  MemberSelectTree ms = (MemberSelectTree) it.getQualifiedIdentifier();
                  String pkg = ms.getExpression().toString() + "." + ms.getIdentifier().toString();
                  if (pkg.equals(importDeclaration)) {
                    return; // Import existiert schon!
                  }
                }
              }
              CompilationUnitTree copy = make.addCompUnitImport(
                      cut,
                      make.Import(make.Identifier(importDeclaration), false));
              workingCopy.rewrite(cut, copy);
            }
          });
          result.commit();
        } catch (IOException ex) {
          Exceptions.printStackTrace(ex);
        }
      }
    }).start();
  }

Im Prinzip ist die Methode aus dem obigen Guide-Tutorial zusammengebaut, nähere Erklärungen finden sich dort. Es sollte auch erwähnt werden, dass ein Erzeugen von imports so eigentlich nicht gemacht werden soll (wird auch im Guide beschrieben)!

Zusammenfassung


Was aber wichtig ist, dass das Verschmelzen von zwei APIs wieder mal nur ein winzig kleiner Befehl ist:
JavaSource source = JavaSource.forDocument (doc);

Die Wirkung ist für den Anwender der Code Completion aber bedeutend.

See Also

Java Context erhalten (en)

Code Completion in any JEditorPane (en)

#1 CodeCompletion Tutorial (en)

#2 How to Create a Caret Aware Code Completion Box (en)

#3 Java Developers Guide (en)

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo