Java Getriebe

Java und NetBeans

Die NetBeans Laufzeitkonfiguration auf einen Blick

Geertjan hatte vor ein paar Tagen schon einen Artikel „Viewing the NetBeans Central Registry“ in seinem Blog eingestellt in dem er mit einen mehr oder weniger Einzeiler das aktuelle „SystemFileSystem“ anzeigen kann. Diese „Zentralen Einstellungen“ (bzw „XML Layer File System“) ist DER Mechanismus der NetBeans Platform um die Modularität der RCP Anwendungen sicher zu stellen. Ohne ihn wäre der deklarative Ansatz der Platform nicht möglich.

Innerhalb der IDE bietet NetBeans zwar die Ansicht „Important Files / XML Layer / <this layer in context>“ an, aber diese Ansicht berücksichtigt nur die aktuelle Suite (und ein paar seiner Abhängigkeiten) um diesen „Context“ zu definieren. Arbeitet man mit mehreren Suiten die nicht zwingend direkte Abhängigkeiten zueinander haben, ist dieser „Context“ leider viel zu oft nicht vollständig. Aus diesem Grund ist es oft viel interessanter den „XML Layer“ so zu sehen, wie NetBeans ihn zur Laufzeit tatsächlich verwendet.

In oben erwähnten Artikel von Geertjan verwendet er einen BeanTreeView um den Baum anzuzeigen. Dies entspricht in etwa dem, was auch die IDE schon im Projektbaum anzeigt. Allerdings „versteckt“ diese Ansicht ein paar Informationen, die den Entwickler interessieren könnten. Dies sind vor allem die „.shadow“ Einträge der Actions oder die „position“ Attribute der Einträge. Für mich zwei Parameter, die zum Debuggen durchaus wichtig sind. Deswegen hatte ich – wie im Kommentar unter dem Artikel erwähnt – schon vor dem Blogartikel eine eigene Version der Darstellung programmiert, die eigene Nodes verwendet, die besagte Informationen bereitstellen. Ausserdem verwende ich ein OutlineView um diese Parameter auch anzeigen zu können.

Wichtigster Teil ist die Klasse „LayerPropertiesNode“, den ich von einem FilterNode abgeleitet habe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class LayerPropertiesNode extends FilterNode
{
  private static boolean isFolder(Node node)
  {
    return null != node.getLookup().lookup(DataFolder.class);
  }

  public LayerPropertiesNode(Node node)
  {
    super(node, isFolder(node) ? Children.create(
        new LayerPropertiesFactory(node), true) : Children.LEAF);
  }

  @Override
  public String getDisplayName()
  {
    return getLookup().lookup(FileObject.class).getName();
  }
  [...]
}

Falls der Originalnode ein DataFolder in seinem Lookup hat, wird eine eigene ChildFactory erstellt, die ich später noch erklären werde. Als DisplayName habe ich hier absichtlich den Namen des FileObjects und nicht des eigentlichen DataObjects verwendet, weil ich so die Einträge deutlich einfacher in meinen Quellen wiederfinden kann.

Die nächste „große“ Zauberei findet in der Methode getPropertySets des FilterNode statt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Override
public Node.PropertySet[] getPropertySets()
{
  Sheet.Set set = Sheet.createPropertiesSet();

  set.put(new PropertySupport.ReadOnly<Integer>(
      "position", Integer.class, "Position", null)
  {
    @Override
    public Integer getValue() throws IllegalAccessException,
        InvocationTargetException
    {
      FileObject fileEntry =
          getLookup().lookup(FileObject.class);
      Integer posValue =
          (Integer)fileEntry.getAttribute("position");
      return posValue != null ? posValue : Integer.valueOf(0);
    }

  });
  set.put(new PropertySupport.ReadOnly<String>(
      "ext", String.class, "Extension", null)
  {
    @Override
    public String getValue() throws IllegalAccessException,
        InvocationTargetException
    {
      FileObject fileEntry =
          getLookup().lookup(FileObject.class);
      return fileEntry.getExt();
    }

  });

  Node.PropertySet[] original = super.getPropertySets();
  Node.PropertySet[] withLayer =
      new Node.PropertySet[original.length + 1];
  System.arraycopy(original, 0, withLayer, 0, original.length);
  withLayer[withLayer.length - 1] = set;
  return withLayer;
}

Hier werden dem Node die beiden Properties „position“ und „ext“ hinzugefügt, die diese Informationen wiederum aus dem FileObject auslesen. Der DataNode des DataObject versteckt diese Infos, so dass dies der vorrangige Grund für den eigenen Node ist. Ausserdem werden alle „.shadow“ Einträge durch den Node des „.instanz“ Eintrags ersetzt so dass Verweise nicht mehr als solche zu erkennen sind. Was dem Laufzeitsystem von NetBeans nur zu Gute kommt, ist zum Debuggen leider nicht geeignet.

Der Vollständigkeit halber zeige ich auch noch mal die wichtigen Methoden der ChildFactory, aber hier passieren eigentlich keine überraschenden Dinge mehr:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
protected boolean createKeys(List<FileObject> list)
{
  FileObject folder =
      context.getLookup().lookup(FileObject.class);

  FileObject[] children = folder.getChildren();
  List<FileObject> ordered =
      FileUtil.getOrder(Arrays.asList(children), false);
  list.addAll(ordered);
  return true;
}

@Override
protected Node createNodeForKey(FileObject key)
{
  AbstractNode node = new AbstractNode(
      org.openide.nodes.Children.LEAF,
      key.isFolder()
      ? Lookups.fixed(key, DataFolder.findFolder(key))
      : Lookups.singleton(key));

  return new LayerPropertiesNode(node);
}

In createKeys hole ich mir einfach nur die children des Fileobjekts und in createNodeForKey erzeuge ich mir einen einfachen Node mit dem FileObject und einer DataFolder Instanz falls vorhanden und verpacke das ganze wieder in meinen FilterNode.

Zu guter Letzt das View. Hier braucht nur die Zeile aus Geertjans Artikel „add(new BeanTreeView(), BorderLayout.CENTER);“ durch die folgenden Zeilen ersetzt werden:

1
2
3
4
OutlineView view = new OutlineView("entry");
view.addPropertyColumn("position", "Position");
view.addPropertyColumn("ext", "Extension");
add(view, BorderLayout.CENTER));

und der Node in den FilterNode verpackt werden:

1
2
3
4
Node node =
    DataObject.find(FileUtil.getConfigRoot()).getNodeDelegate();
node = new LayerPropertiesNode(node);
myExplorerManager.setRootContext(node);

Insgesamt ist es zwar kein Einzeiler mehr, aber dafür bietet mir diese Darstellung deutlich mehr Information zum Debuggen. Bei Bedarf kann die getPropertySets Methode mit anderen Properties erweitert werden, die dann auch im OutlineView angezeigt werden können.

2 Responses to Die NetBeans Laufzeitkonfiguration auf einen Blick

  1. markiewb says:

    Well described – i managed to reproduce your steps and to build it manually.

    But could you please provide this as a plugin in the plugin center, so that other devs should not have to much struggle?

  2. I’m glad this post helped you.

    Currently I am a little busy, but I can do create a plugin when I have some time.

    Jens

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.