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

  • sQLshell Version 7.9pre4 build 11358

    Eine neue Version der sQLshell ist verfügbar!

    Weiterlesen
  • Fallstudie elektronische Signatur

    Ein Influenzer würde einen solchen Artikel beginnen mit "Ich werde immer wieder gefragt, warum..." Da ich keiner bin kann ich so etwas nicht tun – statt dessen erläutere ich einfach das Szenario: Ich pflege diverse Open Source Projekte, die sich mit PKI und kryptographischen Timestamps. Jedoch benutzt die sQLshell elektronische Signaturen nur für die Verteilung der diversen Plugins.

    Weiterlesen
  • Elektronische Signaturen zur Absicherung bei der Migration

    Ich habe bisher einen Aspekt absichtlich und gewissenhaft aus der sQLshell herausgelassen, mich aber jetzt - wo die Zahl der gefundenen Sicherheitslücken in allen möglichen Produkten immer mehr zunimmt - doch einmal damit beschäftigt...

    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.