HexInspector Komponente in Java

13.10.2026

Als ich diese Lösung zur Darstellunbg und Analyse von beliebigen strukturierten Daten fand (das Beispiel erledigt das für ASN.1 kodierte Informationen), fasste ich den Entschluss, dass ich eine entsprechende Komponente in Java realisieren wollte.

Grob gesagt musste also eine Komponente her, die asu zwei Teilen bestand: einem Baum, der die Struktur darstellt und einem daneben befindlichen Element, das die Inhalte als Hexdump (hexadezimal kodierte Bytes) darstellt.

Zwischen beiden Teilen sollte ein Austausch stattfinden: Klickt man einen Knoten des Baums an, werden rechts alle Bytes hervorgehoben, die zu diesem Knoten gehören und klickt man rechts auf ein Byte, so wird der entsprechende Knoten im Baum geöffnet und selektiert.

Dazu musste ein entsprechendes TreeModel geschaffen werden. Für die Darstellung des Baumes benutze ich einen ganz normalen JTree. Für die Anzeige des HexDump kommt ein JTextArea zum Einsatz, in dem man über entsprechende Attribute seines StyledDocument bestimmte Abschnitte hervorheben kann.

Zusammen sieht das zum Beispiel so aus:

Screenshot Beispiel, das die Kodierung eines zufälligen UTF-Strings zeigt

Man sieht, dass ich noch ein wenig von den eingangs formulierten Zielen abgewichen bin: der selektierte Knoten im Baum wird in Pink hervorgehoben und sein unmittelbar übergeordneter Knoten in Gelb.

Dioe Implementierung gebe ich hier an:

import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;

public class HexInspector extends javax.swing.JPanel implements java.awt.event.ComponentListener , javax.swing.event.TreeSelectionListener , java.awt.event.MouseListener { private javax.swing.JTextPane ePane; private javax.swing.JScrollPane scroller; private de.elbosso.model.tree.ByteArrayNode root; private javax.swing.JTree tree;

public HexInspector(de.elbosso.model.tree.ByteArrayNode node) { super(new java.awt.GridLayout(1,2)); ePane=new javax.swing.JTextPane(); ePane.setEditable(false); ePane.setFont(new java.awt.Font(java.awt.Font.MONOSPACED, java.awt.Font.PLAIN, ePane.getFont().getSize())); ePane.setDocument(new javax.swing.text.DefaultStyledDocument()); ePane.addMouseListener(this); scroller=new javax.swing.JScrollPane(ePane); scroller.setHorizontalScrollBarPolicy(scroller.HORIZONTAL_SCROLLBAR_NEVER); scroller.setVerticalScrollBarPolicy(scroller.VERTICAL_SCROLLBAR_ALWAYS); scroller.addComponentListener(this); root=node; tree=new javax.swing.JTree(new javax.swing.tree.DefaultTreeModel(root)); tree.addTreeSelectionListener(this); tree.setEditable(false); javax.swing.JScrollPane scr=new javax.swing.JScrollPane(tree); add(scr); add(scroller); } public void setModel(de.elbosso.model.tree.ByteArrayNode root) { this.root=root; tree.setModel(new javax.swing.tree.DefaultTreeModel(root)); ePane.setText(breakDown((ePane.getVisibleRect().width)/ePane.getFontMetrics(ePane.getFont()).charWidth('M')-1)); } public void addTreeSelectionListener(javax.swing.event.TreeSelectionListener l) { tree.addTreeSelectionListener(l); } public void removeTreeSelectionListener(javax.swing.event.TreeSelectionListener l) { tree.removeTreeSelectionListener(l); } public void removeTreeSelectionListener(javax.swing.tree.TreeCellRenderer r) { tree.setCellRenderer(r); } public java.lang.String breakDown(int charsPerLine) { return new java.lang.String(de.elbosso.util.Utilities.formatHexDumpAsChars(root.getContent(), ' ', charsPerLine)); } public static byte[] charToBytes(final char x) throws java.io.UnsupportedEncodingException { return charToBytes(x,java.nio.charset.Charset.defaultCharset()); } public static byte[] charToBytes(final char x, java.nio.charset.Charset charset) throws java.io.UnsupportedEncodingException { String temp = new String(new char[] {x}); return temp.getBytes(charset); } @Override public void componentResized(ComponentEvent e) { ePane.setText(breakDown((ePane.getVisibleRect().width)/ePane.getFontMetrics(ePane.getFont()).charWidth('M')-1)); } @Override public void componentMoved(ComponentEvent e) { } @Override public void componentShown(ComponentEvent e) { } @Override public void componentHidden(ComponentEvent e) { } @Override public void valueChanged(TreeSelectionEvent e) { clearAttributes(); if(e.isAddedPath()) { de.elbosso.model.tree.ByteArrayNode ban=(de.elbosso.model.tree.ByteArrayNode)e.getPath().getParentPath().getLastPathComponent(); highlight(ban,true,java.awt.Color.YELLOW); ban=(de.elbosso.model.tree.ByteArrayNode)e.getPath().getLastPathComponent(); highlight(ban,true,java.awt.Color.PINK); } } @Override public void mouseClicked(MouseEvent e) { int byteIndex=ePane.viewToModel(e.getPoint())/3; javax.swing.tree.TreeNode target=getNodeForByte(root,byteIndex); javax.swing.tree.TreePath tp=toTreePath(target); expandNode(tree.getModel(),tp.getLastPathComponent(),tp); tree.scrollPathToVisible(tp); tree.getSelectionModel().setSelectionPath(tp); } private void clearAttributes() { javax.swing.text.StyledDocument sd=(javax.swing.text.StyledDocument)ePane.getDocument(); MutableAttributeSet attr = new SimpleAttributeSet(); sd.setCharacterAttributes(0,root.getContent().length*3,attr,true); } private void highlight(de.elbosso.model.tree.ByteArrayNode node, boolean bold, java.awt.Color color) { javax.swing.text.StyledDocument sd=(javax.swing.text.StyledDocument)ePane.getDocument(); MutableAttributeSet attr = new SimpleAttributeSet(); StyleConstants.setBold (attr, bold); StyleConstants.setBackground(attr,color); sd.setCharacterAttributes(node.getStartOffset()*3,node.getBateLength()*3-1,attr,true); } private javax.swing.tree.TreePath toTreePath(javax.swing.tree.TreeNode node) { java.util.List<javax.swing.tree.TreeNode> l=new java.util.LinkedList(); l.add(node); while(node.getParent()!=null) { node=node.getParent(); l.add(node); } java.util.Collections.reverse(l); javax.swing.tree.TreePath tp=new javax.swing.tree.TreePath(l.toArray()); return tp; } protected void expandNode(javax.swing.tree.TreeModel m,java.lang.Object o,javax.swing.tree.TreePath p) { if(m!=null) { tree.expandPath(p); if(p.getParentPath()!=null) { expandNode(m,o,p.getParentPath()); } } } private de.elbosso.model.tree.ByteArrayNode getNodeForByte(de.elbosso.model.tree.ByteArrayNode current,int byteIndex) { de.elbosso.model.tree.ByteArrayNode rv=current; if(current.isLeaf()==false) { java.util.Enumeration en=current.children(); while(en.hasMoreElements()) { de.elbosso.model.tree.ByteArrayNode ban=(de.elbosso.model.tree.ByteArrayNode)en.nextElement(); if((ban.getStartOffset()<=byteIndex)&&(ban.getStartOffset()+ban.getBateLength()>byteIndex)) { rv=getNodeForByte(ban, byteIndex); break; } } } return rv; } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } }

Die wichtige Klasse ByteArrayNode ist hier zu finden:

import javax.swing.tree.TreeNode;

public class ByteArrayNode extends de.elbosso.model.tree.CustomNode { private byte[] content; private int startOffset; protected java.util.List<ByteArrayNode> children;

public ByteArrayNode(int startOffset,java.lang.String content, javax.swing.tree.TreeNode parent) { super(content,parent); this.startOffset=startOffset; this.content=content.getBytes(); children=new java.util.LinkedList(); } public int getStartOffset() { return startOffset; } public int getBateLength() { return this.content.length; } public byte[] getContent() { return content; } @Override public TreeNode getChildAt(int index) { return children.get(index); } @Override public boolean isLeaf() { return children.isEmpty(); } @Override public int getChildCount() { return children.size(); } @Override public boolean getAllowsChildren() { return !isLeaf(); } @Override public java.util.Enumeration children() { // System.out.println("++++++++++++++"); return java.util.Collections.enumeration(children); } }

Alle Artikel rss Wochenübersicht Monatsübersicht Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Precision Time Protocol

    15.10.2021

    Durch meinen Neffen wurde ich neulich auf ein Video im Internet aufmerksam gemacht, aus dem ich von der Existenz des Protokolls PTP oder Precision Time Protocol erfuhr. Hier einige Gedanken dazu

    Weiterlesen

Neueste Artikel

  • Präsentation zum Thema: Certificate and Signature Use Cases

    Eine Präsentation, die einige der Gesichtspunkte beleuchtet, die man beachten sollte, wenn man sich für elektronische Signaturen und Zertifikate in der eigenen Anwendung entscheidet. Weiterhin werden Möglichkeiten zur Benutzung aufgezeigt, die etwas weiter vom Mainstream entfernt sind.

    Weiterlesen
  • LinkCollections 2026 XI

    Nach der letzten losen Zusammenstellung (für mich) interessanter Links aus den Tiefen des Internet von 2026 folgt hier gleich die nächste:

    Weiterlesen
  • sQLshell Version 7.9pre4 build 11358

    Eine neue Version der sQLshell ist verfügbar!

    Weiterlesen

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen Codeberg-Repositories findet man hier.