commit
5f1e75c284
10
pom.xml
10
pom.xml
|
@ -372,6 +372,16 @@
|
|||
<artifactId>org.abego.treelayout.core</artifactId>
|
||||
<version>${treelayout.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.javaparser</groupId>
|
||||
<artifactId>javaparser-core</artifactId>
|
||||
<version>3.26.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.javaparser</groupId>
|
||||
<artifactId>javaparser-symbol-solver-core</artifactId>
|
||||
<version>3.26.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- TODO Re-add for Graal.JS support -->
|
||||
<!--<dependency>
|
||||
|
|
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
* BSD 3-Clause "New" or "Revised" License
|
||||
*
|
||||
* Copyright (c) 2021, Robert Futrell All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
* following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
||||
* following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
||||
* following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package the.bytecode.club.bytecodeviewer.gui.components;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.DocumentRange;
|
||||
import org.fife.ui.rsyntaxtextarea.ErrorStrip;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
|
||||
import org.fife.ui.rsyntaxtextarea.parser.Parser;
|
||||
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This is based on {@link ErrorStrip}, but with our own implementations to work with how occurrences are marked on
|
||||
* the text area.
|
||||
* <p>
|
||||
* Created by Bl3nd.
|
||||
* Date: 8/26/2024
|
||||
*/
|
||||
public class MyErrorStripe extends JPanel
|
||||
{
|
||||
private final RSyntaxTextArea textArea;
|
||||
private final transient Listener listener;
|
||||
|
||||
public MyErrorStripe(RSyntaxTextArea textArea)
|
||||
{
|
||||
this.textArea = textArea;
|
||||
setLayout(null);
|
||||
listener = new Listener();
|
||||
addMouseListener(listener);
|
||||
}
|
||||
|
||||
private int lineToY(int line, Rectangle r)
|
||||
{
|
||||
if (r == null)
|
||||
r = new Rectangle();
|
||||
|
||||
textArea.computeVisibleRect(r);
|
||||
int h = r.height;
|
||||
float lineCount = textArea.getLineCount();
|
||||
int lineHeight = textArea.getLineHeight();
|
||||
int linesPerVisibleRect = h / lineHeight;
|
||||
return Math.round((h - 1) * line / Math.max(lineCount, linesPerVisibleRect));
|
||||
}
|
||||
|
||||
private int yToLine(int y)
|
||||
{
|
||||
int line = -1;
|
||||
int h = textArea.getVisibleRect().height;
|
||||
int lineHeight = textArea.getLineHeight();
|
||||
int linesPerVisibleRect = h / lineHeight;
|
||||
int lineCount = textArea.getLineCount();
|
||||
if (y < h)
|
||||
{
|
||||
float at = y / (float) h;
|
||||
line = Math.round((Math.max(lineCount, linesPerVisibleRect) - 1) * at);
|
||||
}
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
private void paintParserNoticeMarker(Graphics2D g, ParserNotice notice, int width, int height)
|
||||
{
|
||||
Color borderColor = notice.getColor();
|
||||
if (borderColor == null)
|
||||
borderColor = Color.BLACK;
|
||||
|
||||
Color fillColor = borderColor.brighter();
|
||||
g.setColor(fillColor);
|
||||
g.fillRect(0, 0, width, height);
|
||||
|
||||
g.setColor(borderColor);
|
||||
g.drawRect(0, 0, width - 1, height - 1);
|
||||
}
|
||||
|
||||
public void refreshMarkers()
|
||||
{
|
||||
removeAll();
|
||||
Map<Integer, Marker> markerMap = new HashMap<>();
|
||||
List<DocumentRange> occurrences = textArea.getMarkedOccurrences();
|
||||
addMarkersForRanges(occurrences, markerMap, textArea.getMarkOccurrencesColor());
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void addMarkersForRanges(List<DocumentRange> occurrences, Map<Integer, Marker> markerMap, Color color)
|
||||
{
|
||||
for (DocumentRange range : occurrences)
|
||||
{
|
||||
int line;
|
||||
try
|
||||
{
|
||||
line = textArea.getLineOfOffset(range.getStartOffset());
|
||||
} catch (BadLocationException e)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ParserNotice notice = new MarkedOccurrenceNotice(range, color);
|
||||
Integer key = line;
|
||||
Marker m = markerMap.get(key);
|
||||
if (m == null)
|
||||
{
|
||||
m = new Marker(notice);
|
||||
m.addMouseListener(listener);
|
||||
markerMap.put(key, m);
|
||||
add(m);
|
||||
} else
|
||||
{
|
||||
if (!m.containsMarkedOccurrence())
|
||||
m.addNotice(notice);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateUI()
|
||||
{
|
||||
super.updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g)
|
||||
{
|
||||
super.paintComponent(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintChildren(Graphics g)
|
||||
{
|
||||
super.paintChildren(g);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getPreferredSize()
|
||||
{
|
||||
return new Dimension(14, textArea.getPreferredScrollableViewportSize().height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doLayout()
|
||||
{
|
||||
for (int i = 0; i < getComponentCount(); i++)
|
||||
{
|
||||
Marker m = (Marker) getComponent(i);
|
||||
m.updateLocation();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNotify()
|
||||
{
|
||||
super.addNotify();
|
||||
refreshMarkers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeNotify()
|
||||
{
|
||||
super.removeNotify();
|
||||
}
|
||||
|
||||
private class Listener extends MouseAdapter
|
||||
{
|
||||
private final Rectangle r = new Rectangle();
|
||||
|
||||
@Override
|
||||
public void mouseClicked(@NotNull MouseEvent e)
|
||||
{
|
||||
Component source = (Component) e.getSource();
|
||||
if (source instanceof MyErrorStripe.Marker)
|
||||
{
|
||||
Marker m = (Marker) source;
|
||||
m.mouseClicked(e);
|
||||
return;
|
||||
}
|
||||
|
||||
int line = yToLine(e.getY());
|
||||
if (line > -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
int offset = textArea.getLineOfOffset(line);
|
||||
textArea.setCaretPosition(offset);
|
||||
RSyntaxUtilities.selectAndPossiblyCenter(textArea, new DocumentRange(offset, offset), false);
|
||||
} catch (BadLocationException exception)
|
||||
{
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class MarkedOccurrenceNotice implements ParserNotice
|
||||
{
|
||||
private final DocumentRange range;
|
||||
private final Color color;
|
||||
|
||||
MarkedOccurrenceNotice(DocumentRange range, Color color)
|
||||
{
|
||||
this.range = range;
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsPosition(int pos)
|
||||
{
|
||||
return pos >= range.getStartOffset() && pos < range.getEndOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Color getColor()
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLength()
|
||||
{
|
||||
return range.getEndOffset() - range.getStartOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Level getLevel()
|
||||
{
|
||||
return Level.INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getLine()
|
||||
{
|
||||
try
|
||||
{
|
||||
return textArea.getLineOfOffset(range.getStartOffset()) + 1;
|
||||
} catch (BadLocationException e)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getKnowsOffsetAndLength()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@Contract(pure = true)
|
||||
@Override
|
||||
public @NotNull String getMessage()
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOffset()
|
||||
{
|
||||
return range.getStartOffset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Parser getParser()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean getShowInEditor()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull ParserNotice o)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static final int MARKER_HEIGHT = 3;
|
||||
|
||||
private class Marker extends JComponent
|
||||
{
|
||||
private final java.util.List<ParserNotice> notices;
|
||||
|
||||
Marker(ParserNotice notice)
|
||||
{
|
||||
notices = new ArrayList<>();
|
||||
addNotice(notice);
|
||||
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
||||
setSize(getPreferredSize());
|
||||
}
|
||||
|
||||
private void addNotice(ParserNotice notice)
|
||||
{
|
||||
notices.add(notice);
|
||||
}
|
||||
|
||||
@Contract(value = " -> new", pure = true)
|
||||
@Override
|
||||
public @NotNull Dimension getPreferredSize()
|
||||
{
|
||||
return new Dimension(12, MARKER_HEIGHT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void paintComponent(Graphics g)
|
||||
{
|
||||
final ParserNotice notice = getHighestPriorityNotice();
|
||||
if (notice != null)
|
||||
paintParserNoticeMarker((Graphics2D) g, notice, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
protected void mouseClicked(MouseEvent e)
|
||||
{
|
||||
ParserNotice pn = notices.get(0);
|
||||
int offs = pn.getOffset();
|
||||
int len = pn.getLength();
|
||||
if (offs > -1 && len > -1) // These values are optional
|
||||
{
|
||||
DocumentRange range = new DocumentRange(offs, offs + len);
|
||||
RSyntaxUtilities.selectAndPossiblyCenter(textArea, range, true);
|
||||
} else
|
||||
{
|
||||
int line = pn.getLine();
|
||||
try
|
||||
{
|
||||
offs = textArea.getLineStartOffset(line);
|
||||
textArea.getFoldManager().ensureOffsetNotInClosedFold(offs);
|
||||
textArea.setCaretPosition(offs);
|
||||
} catch (BadLocationException ble) // Never happens
|
||||
{
|
||||
UIManager.getLookAndFeel().provideErrorFeedback(textArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean containsMarkedOccurrence()
|
||||
{
|
||||
boolean result = false;
|
||||
for (ParserNotice notice : notices)
|
||||
{
|
||||
if (notice instanceof MarkedOccurrenceNotice)
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public ParserNotice getHighestPriorityNotice()
|
||||
{
|
||||
ParserNotice selectedNotice = null;
|
||||
int lowestLevel = Integer.MAX_VALUE;
|
||||
for (ParserNotice notice : notices)
|
||||
{
|
||||
if (notice.getLevel().getNumericValue() < lowestLevel)
|
||||
{
|
||||
lowestLevel = notice.getLevel().getNumericValue();
|
||||
selectedNotice = notice;
|
||||
}
|
||||
}
|
||||
|
||||
return selectedNotice;
|
||||
}
|
||||
|
||||
public void updateLocation()
|
||||
{
|
||||
int line = notices.get(0).getLine();
|
||||
int y = lineToY(line - 1, null);
|
||||
setLocation(2, y);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* BSD 3-Clause "New" or "Revised" License
|
||||
*
|
||||
* Copyright (c) 2021, Robert Futrell All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
* following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
||||
* following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
||||
* following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package the.bytecode.club.bytecodeviewer.gui.components;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.DocumentRange;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter;
|
||||
import org.fife.ui.rsyntaxtextarea.parser.ParserNotice;
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.plaf.TextUI;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.JTextComponent;
|
||||
import javax.swing.text.View;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Extension from RSyntaxTextArea
|
||||
*/
|
||||
public class RSyntaxTextAreaHighlighterEx extends RSyntaxTextAreaHighlighter
|
||||
{
|
||||
private final List<SyntaxLayeredHighlightInfoImpl> markedOccurrences = new ArrayList<>();
|
||||
private static final Color DEFAULT_PARSER_NOTICE_COLOR = Color.RED;
|
||||
|
||||
public Object addMarkedOccurrenceHighlight(int start, int end, @NotNull SmartHighlightPainter p) throws BadLocationException
|
||||
{
|
||||
Document doc = textArea.getDocument();
|
||||
TextUI mapper = textArea.getUI();
|
||||
// Always layered highlights for marked occurrences.
|
||||
SyntaxLayeredHighlightInfoImpl i = new SyntaxLayeredHighlightInfoImpl();
|
||||
p.setPaint(UIManager.getColor("ScrollBar.thumb"));
|
||||
i.setPainter(p);
|
||||
i.setStartOffset(doc.createPosition(start));
|
||||
// HACK: Use "end-1" to prevent chars the user types at the "end" of
|
||||
// the highlight to be absorbed into the highlight (default Highlight
|
||||
// behavior).
|
||||
i.setEndOffset(doc.createPosition(end - 1));
|
||||
markedOccurrences.add(i);
|
||||
mapper.damageRange(textArea, start, end);
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DocumentRange> getMarkedOccurrences()
|
||||
{
|
||||
List<DocumentRange> list = new ArrayList<>(markedOccurrences.size());
|
||||
for (HighlightInfo info : markedOccurrences)
|
||||
{
|
||||
int start = info.getStartOffset();
|
||||
int end = info.getEndOffset() + 1; // HACK
|
||||
if (start <= end)
|
||||
{
|
||||
// Occasionally a Marked Occurrence can have a lost end offset
|
||||
// but not start offset (replacing entire text content with
|
||||
// new content, and a marked occurrence is on the last token
|
||||
// in the document).
|
||||
DocumentRange range = new DocumentRange(start, end);
|
||||
list.add(range);
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
public void clearMarkOccurrencesHighlights()
|
||||
{
|
||||
// Don't remove via an iterator; since our List is an ArrayList, this
|
||||
// implies tons of System.arrayCopy()s
|
||||
for (HighlightInfo info : markedOccurrences)
|
||||
{
|
||||
repaintListHighlight(info);
|
||||
}
|
||||
|
||||
markedOccurrences.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintLayeredHighlights(Graphics g, int lineStart, int lineEnd, Shape viewBounds, JTextComponent editor, View view)
|
||||
{
|
||||
paintListLayered(g, lineStart, lineEnd, viewBounds, editor, view, markedOccurrences);
|
||||
super.paintLayeredHighlights(g, lineStart, lineEnd, viewBounds, editor, view);
|
||||
}
|
||||
|
||||
private static class SyntaxLayeredHighlightInfoImpl extends LayeredHighlightInfoImpl
|
||||
{
|
||||
private ParserNotice notice;
|
||||
|
||||
@Override
|
||||
public Color getColor()
|
||||
{
|
||||
Color color = null;
|
||||
if (notice != null)
|
||||
{
|
||||
color = notice.getColor();
|
||||
if (color == null)
|
||||
color = DEFAULT_PARSER_NOTICE_COLOR;
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "[SyntaxLayeredHighlightInfoImpl: startOffs=" + getStartOffset() + ", endOffs=" + getEndOffset() + ", color=" + getColor() + "]";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,197 +18,187 @@
|
|||
|
||||
package the.bytecode.club.bytecodeviewer.gui.components;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseWheelEvent;
|
||||
import java.awt.event.MouseWheelListener;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextField;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.text.BadLocationException;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||
import the.bytecode.club.bytecodeviewer.BytecodeViewer;
|
||||
import the.bytecode.club.bytecodeviewer.Configuration;
|
||||
import the.bytecode.club.bytecodeviewer.GlobalHotKeys;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.listeners.PressKeyListener;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.listeners.ReleaseKeyListener;
|
||||
import the.bytecode.club.bytecodeviewer.gui.theme.LAFTheme;
|
||||
import the.bytecode.club.bytecodeviewer.resources.IconResources;
|
||||
import the.bytecode.club.bytecodeviewer.translation.TranslatedComponents;
|
||||
import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJCheckBox;
|
||||
import the.bytecode.club.bytecodeviewer.util.JTextAreaUtils;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
* Searching on an RSyntaxTextArea using swing highlighting
|
||||
*
|
||||
* @author Konloch
|
||||
* @since 6/25/2021
|
||||
*/
|
||||
public class SearchableRSyntaxTextArea extends RSyntaxTextArea {
|
||||
public class SearchableRSyntaxTextArea extends RSyntaxTextArea
|
||||
{
|
||||
|
||||
private RTextScrollPane scrollPane = new RTextScrollPane(this);
|
||||
private final JPanel searchPanel = new JPanel(new BorderLayout());
|
||||
private final JTextField searchInput = new JTextField();
|
||||
private final JCheckBox caseSensitiveSearch = new TranslatedJCheckBox("Match case", TranslatedComponents.MATCH_CASE);
|
||||
private final JLabel titleHeader = new JLabel("");
|
||||
private final Color darkScrollBackground = new Color(0x3c3f41);
|
||||
private final Color darkScrollForeground = new Color(0x575859);
|
||||
private final Color blackScrollBackground = new Color(0x232323);
|
||||
private final Color blackScrollForeground = new Color(0x575859);
|
||||
private Runnable onCtrlS;
|
||||
private RTextScrollPane scrollPane = new RTextScrollPane(this);
|
||||
private final TextAreaSearchPanel textAreaSearchPanel;
|
||||
private final Color darkScrollBackground = new Color(0x3c3f41);
|
||||
private final Color darkScrollForeground = new Color(0x575859);
|
||||
private final Color blackScrollBackground = new Color(0x232323);
|
||||
private final Color blackScrollForeground = new Color(0x575859);
|
||||
private Runnable onCtrlS;
|
||||
|
||||
public SearchableRSyntaxTextArea() {
|
||||
if (Configuration.lafTheme == LAFTheme.HIGH_CONTRAST_DARK) {
|
||||
//this fixes the white border on the jScrollBar panes
|
||||
scrollPane.getHorizontalScrollBar().setBackground(blackScrollBackground);
|
||||
scrollPane.getHorizontalScrollBar().setForeground(blackScrollForeground);
|
||||
scrollPane.getVerticalScrollBar().setBackground(blackScrollBackground);
|
||||
scrollPane.getVerticalScrollBar().setForeground(blackScrollForeground);
|
||||
} else if (Configuration.lafTheme.isDark()) {
|
||||
//this fixes the white border on the jScrollBar panes
|
||||
scrollPane.getHorizontalScrollBar().setBackground(darkScrollBackground);
|
||||
scrollPane.getHorizontalScrollBar().setForeground(darkScrollForeground);
|
||||
scrollPane.getVerticalScrollBar().setBackground(darkScrollBackground);
|
||||
scrollPane.getVerticalScrollBar().setForeground(darkScrollForeground);
|
||||
}
|
||||
public SearchableRSyntaxTextArea()
|
||||
{
|
||||
if (Configuration.lafTheme == LAFTheme.HIGH_CONTRAST_DARK)
|
||||
{
|
||||
//this fixes the white border on the jScrollBar panes
|
||||
scrollPane.getHorizontalScrollBar().setBackground(blackScrollBackground);
|
||||
scrollPane.getHorizontalScrollBar().setForeground(blackScrollForeground);
|
||||
scrollPane.getVerticalScrollBar().setBackground(blackScrollBackground);
|
||||
scrollPane.getVerticalScrollBar().setForeground(blackScrollForeground);
|
||||
} else if (Configuration.lafTheme.isDark())
|
||||
{
|
||||
//this fixes the white border on the jScrollBar panes
|
||||
scrollPane.getHorizontalScrollBar().setBackground(darkScrollBackground);
|
||||
scrollPane.getHorizontalScrollBar().setForeground(darkScrollForeground);
|
||||
scrollPane.getVerticalScrollBar().setBackground(darkScrollBackground);
|
||||
scrollPane.getVerticalScrollBar().setForeground(darkScrollForeground);
|
||||
}
|
||||
|
||||
setAntiAliasingEnabled(true);
|
||||
this.textAreaSearchPanel = new TextAreaSearchPanel(this);
|
||||
|
||||
scrollPane.setColumnHeaderView(searchPanel);
|
||||
setAntiAliasingEnabled(true);
|
||||
|
||||
JButton searchNext = new JButton();
|
||||
JButton searchPrev = new JButton();
|
||||
JPanel buttonPane = new JPanel(new BorderLayout());
|
||||
buttonPane.add(searchNext, BorderLayout.WEST);
|
||||
buttonPane.add(searchPrev, BorderLayout.EAST);
|
||||
searchNext.setIcon(IconResources.nextIcon);
|
||||
searchPrev.setIcon(IconResources.prevIcon);
|
||||
searchPanel.add(buttonPane, BorderLayout.WEST);
|
||||
searchPanel.add(searchInput, BorderLayout.CENTER);
|
||||
searchPanel.add(caseSensitiveSearch, BorderLayout.EAST);
|
||||
addKeyListener(new PressKeyListener(keyEvent ->
|
||||
{
|
||||
if ((keyEvent.getKeyCode() == KeyEvent.VK_F) && ((keyEvent.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0))
|
||||
this.textAreaSearchPanel.getSearchInput().requestFocusInWindow();
|
||||
|
||||
searchNext.addActionListener(arg0 -> search(searchInput.getText(), true, caseSensitiveSearch.isSelected()));
|
||||
searchPrev.addActionListener(arg0 -> search(searchInput.getText(), false, caseSensitiveSearch.isSelected()));
|
||||
if (onCtrlS != null && (keyEvent.getKeyCode() == KeyEvent.VK_S) && ((keyEvent.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0))
|
||||
{
|
||||
onCtrlS.run();
|
||||
return;
|
||||
}
|
||||
|
||||
searchInput.addKeyListener(new ReleaseKeyListener(keyEvent ->
|
||||
{
|
||||
if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER)
|
||||
search(searchInput.getText(), true, caseSensitiveSearch.isSelected());
|
||||
}));
|
||||
GlobalHotKeys.keyPressed(keyEvent);
|
||||
}));
|
||||
|
||||
addKeyListener(new PressKeyListener(keyEvent ->
|
||||
{
|
||||
if ((keyEvent.getKeyCode() == KeyEvent.VK_F) && ((keyEvent.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0))
|
||||
searchInput.requestFocus();
|
||||
setCursor(new Cursor(Cursor.TEXT_CURSOR));
|
||||
getCaret().setBlinkRate(0);
|
||||
getCaret().setVisible(true);
|
||||
addFocusListener(new FocusAdapter()
|
||||
{
|
||||
@Override
|
||||
public void focusGained(FocusEvent e)
|
||||
{
|
||||
getCaret().setVisible(true);
|
||||
}
|
||||
|
||||
if (onCtrlS != null && (keyEvent.getKeyCode() == KeyEvent.VK_S) && ((keyEvent.getModifiersEx() & KeyEvent.CTRL_DOWN_MASK) != 0)) {
|
||||
onCtrlS.run();
|
||||
return;
|
||||
}
|
||||
@Override
|
||||
public void focusLost(FocusEvent e)
|
||||
{
|
||||
getCaret().setVisible(true);
|
||||
}
|
||||
});
|
||||
|
||||
GlobalHotKeys.keyPressed(keyEvent);
|
||||
}));
|
||||
final Font newFont = getFont().deriveFont((float) BytecodeViewer.viewer.getFontSize());
|
||||
|
||||
final Font newFont = getFont().deriveFont((float) BytecodeViewer.viewer.getFontSize());
|
||||
//set number-bar font
|
||||
setFont(newFont);
|
||||
|
||||
//set number-bar font
|
||||
setFont(newFont);
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//attach CTRL + Mouse Wheel Zoom
|
||||
attachCtrlMouseWheelZoom();
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
//attach CTRL + Mouse Wheel Zoom
|
||||
attachCtrlMouseWheelZoom();
|
||||
//set text font
|
||||
setFont(newFont);
|
||||
});
|
||||
|
||||
//set text font
|
||||
setFont(newFont);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
public void search(String search, boolean forwardSearchDirection, boolean caseSensitiveSearch)
|
||||
{
|
||||
JTextAreaUtils.search(this, search, forwardSearchDirection, caseSensitiveSearch);
|
||||
}
|
||||
|
||||
public void search(String search, boolean forwardSearchDirection, boolean caseSensitiveSearch) {
|
||||
JTextAreaUtils.search(this, search, forwardSearchDirection, caseSensitiveSearch);
|
||||
}
|
||||
public void highlight(String pattern, boolean caseSensitiveSearch)
|
||||
{
|
||||
JTextAreaUtils.highlight(this, pattern, caseSensitiveSearch);
|
||||
}
|
||||
|
||||
public void highlight(String pattern, boolean caseSensitiveSearch) {
|
||||
JTextAreaUtils.highlight(this, pattern, caseSensitiveSearch);
|
||||
}
|
||||
public void attachCtrlMouseWheelZoom()
|
||||
{
|
||||
scrollPane.addMouseWheelListener(e -> {
|
||||
if (getText().isEmpty()) return;
|
||||
if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0)
|
||||
{
|
||||
Font font = getFont();
|
||||
int size = font.getSize();
|
||||
if (e.getWheelRotation() > 0)
|
||||
setFont(new Font(font.getName(), font.getStyle(), --size >= 2 ? --size : 2));
|
||||
else
|
||||
setFont(new Font(font.getName(), font.getStyle(), ++size));
|
||||
|
||||
public void attachCtrlMouseWheelZoom() {
|
||||
scrollPane.addMouseWheelListener(e -> {
|
||||
if (getText().isEmpty()) return;
|
||||
if ((e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) != 0) {
|
||||
Font font = getFont();
|
||||
int size = font.getSize();
|
||||
if (e.getWheelRotation() > 0)
|
||||
setFont(new Font(font.getName(), font.getStyle(), --size >= 2 ? --size : 2));
|
||||
else
|
||||
setFont(new Font(font.getName(), font.getStyle(), ++size));
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
scrollPane = new RTextScrollPane()
|
||||
{
|
||||
@Override
|
||||
protected void processMouseWheelEvent(MouseWheelEvent event)
|
||||
{
|
||||
if (!isWheelScrollingEnabled())
|
||||
{
|
||||
if (getParent() != null)
|
||||
{
|
||||
getParent().dispatchEvent(SwingUtilities.convertMouseEvent(this, event, getParent()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scrollPane = new RTextScrollPane() {
|
||||
@Override
|
||||
protected void processMouseWheelEvent(MouseWheelEvent event) {
|
||||
if (!isWheelScrollingEnabled()) {
|
||||
if (getParent() != null) {
|
||||
getParent().dispatchEvent(SwingUtilities.convertMouseEvent(this, event, getParent()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.processMouseWheelEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
super.processMouseWheelEvent(event);
|
||||
}
|
||||
};
|
||||
scrollPane.setWheelScrollingEnabled(false);
|
||||
}
|
||||
|
||||
scrollPane.setWheelScrollingEnabled(false);
|
||||
}
|
||||
public String getLineText(int line)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (line < getLineCount())
|
||||
{
|
||||
int start = getLineStartOffset(line);
|
||||
int end = getLineEndOffset(line);
|
||||
return getText(start, end - start).trim();
|
||||
}
|
||||
} catch (BadLocationException ignored)
|
||||
{
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public String getLineText(int line) {
|
||||
try {
|
||||
if (line < getLineCount()) {
|
||||
int start = getLineStartOffset(line);
|
||||
int end = getLineEndOffset(line);
|
||||
return getText(start, end - start).trim();
|
||||
}
|
||||
} catch (BadLocationException ignored) {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
public void setOnCtrlS(Runnable onCtrlS)
|
||||
{
|
||||
this.onCtrlS = onCtrlS;
|
||||
}
|
||||
|
||||
public void setOnCtrlS(Runnable onCtrlS) {
|
||||
this.onCtrlS = onCtrlS;
|
||||
}
|
||||
public RTextScrollPane getScrollPane()
|
||||
{
|
||||
return scrollPane;
|
||||
}
|
||||
|
||||
public RTextScrollPane getScrollPane() {
|
||||
return scrollPane;
|
||||
}
|
||||
public TextAreaSearchPanel getTextAreaSearchPanel()
|
||||
{
|
||||
return textAreaSearchPanel;
|
||||
}
|
||||
|
||||
public JPanel getSearchPanel() {
|
||||
return searchPanel;
|
||||
}
|
||||
|
||||
public JTextField getSearchInput() {
|
||||
return searchInput;
|
||||
}
|
||||
|
||||
public JCheckBox getCaseSensitiveSearch() {
|
||||
return caseSensitiveSearch;
|
||||
}
|
||||
|
||||
public JLabel getTitleHeader() {
|
||||
return titleHeader;
|
||||
}
|
||||
|
||||
public Runnable getOnCtrlS() {
|
||||
return onCtrlS;
|
||||
}
|
||||
public Runnable getOnCtrlS()
|
||||
{
|
||||
return onCtrlS;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/***************************************************************************
|
||||
* Bytecode Viewer (BCV) - Java & Android Reverse Engineering Suite *
|
||||
* Copyright (C) 2014 Konloch - Konloch.com / BytecodeViewer.com *
|
||||
* *
|
||||
* This program is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation, either version 3 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
||||
***************************************************************************/
|
||||
package the.bytecode.club.bytecodeviewer.gui.components;
|
||||
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.listeners.ReleaseKeyListener;
|
||||
import the.bytecode.club.bytecodeviewer.resources.IconResources;
|
||||
import the.bytecode.club.bytecodeviewer.translation.TranslatedComponents;
|
||||
import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJCheckBox;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyEvent;
|
||||
|
||||
/**
|
||||
* This panel represents the decompiler name and search box on the top of every {@link the.bytecode.club.bytecodeviewer.gui.resourceviewer.BytecodeViewPanel}
|
||||
* <p>
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/6/2024
|
||||
*/
|
||||
public class TextAreaSearchPanel extends JPanel
|
||||
{
|
||||
private final JCheckBox caseSensitiveSearch = new TranslatedJCheckBox("Match case", TranslatedComponents.MATCH_CASE);
|
||||
private final JLabel titleHeader = new JLabel("");
|
||||
private final JTextField searchInput = new JTextField();
|
||||
private final JTextArea textArea;
|
||||
|
||||
public TextAreaSearchPanel(JTextArea textArea)
|
||||
{
|
||||
super(new BorderLayout());
|
||||
this.textArea = textArea;
|
||||
setup();
|
||||
setVisible(true);
|
||||
}
|
||||
|
||||
private void setup()
|
||||
{
|
||||
this.add(titleHeader, BorderLayout.NORTH);
|
||||
|
||||
JPanel searchPanel = new JPanel();
|
||||
searchPanel.setLayout(new BoxLayout(searchPanel, BoxLayout.X_AXIS));
|
||||
|
||||
searchPanel.add(Box.createHorizontalStrut(35));
|
||||
JButton searchNext = new JButton(IconResources.nextIcon);
|
||||
searchPanel.add(searchNext);
|
||||
searchNext.addActionListener(arg0 -> ((SearchableRSyntaxTextArea) textArea).search(searchInput.getText(), true, caseSensitiveSearch.isSelected()));
|
||||
|
||||
JButton searchPrev = new JButton(IconResources.prevIcon);
|
||||
searchPanel.add(searchPrev);
|
||||
searchPrev.addActionListener(arg0 -> ((SearchableRSyntaxTextArea) textArea).search(searchInput.getText(), false, caseSensitiveSearch.isSelected()));
|
||||
|
||||
searchPanel.add(searchInput);
|
||||
searchInput.addKeyListener(new ReleaseKeyListener(keyEvent ->
|
||||
{
|
||||
if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER)
|
||||
((SearchableRSyntaxTextArea) textArea).search(searchInput.getText(), true, caseSensitiveSearch.isSelected());
|
||||
}));
|
||||
|
||||
searchPanel.add(caseSensitiveSearch);
|
||||
|
||||
// This is needed to add more room to the right of the sensitive search check box
|
||||
searchPanel.add(Box.createHorizontalStrut(2));
|
||||
|
||||
this.add(searchPanel, BorderLayout.SOUTH);
|
||||
}
|
||||
|
||||
public JLabel getTitleHeader()
|
||||
{
|
||||
return titleHeader;
|
||||
}
|
||||
|
||||
public JTextField getSearchInput()
|
||||
{
|
||||
return searchInput;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,348 @@
|
|||
package the.bytecode.club.bytecodeviewer.gui.components.actions;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import the.bytecode.club.bytecodeviewer.BytecodeViewer;
|
||||
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.BytecodeViewPanel;
|
||||
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ClassViewer;
|
||||
import the.bytecode.club.bytecodeviewer.gui.util.BytecodeViewPanelUpdater;
|
||||
import the.bytecode.club.bytecodeviewer.resources.ResourceContainer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.ClassFileContainer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassFieldLocation;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassMethodLocation;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassReferenceLocation;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.parser.TokenUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.text.Element;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* This action is triggered by a user typing (CTRL+B). This goes to a specific variables declaration whether it be in the opened class, or a class within the jar.
|
||||
* <p>
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/7/2024
|
||||
*/
|
||||
public class GoToAction extends AbstractAction
|
||||
{
|
||||
private final ClassFileContainer container;
|
||||
|
||||
public GoToAction(ClassFileContainer classFileContainer)
|
||||
{
|
||||
this.container = classFileContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e)
|
||||
{
|
||||
RSyntaxTextArea textArea = (RSyntaxTextArea) e.getSource();
|
||||
int line = textArea.getCaretLineNumber() + 1;
|
||||
int column = textArea.getCaretOffsetFromLineStart();
|
||||
|
||||
container.fieldMembers.values().forEach(fields -> fields.forEach(field -> {
|
||||
if (field.line == line && field.columnStart - 1 <= column && field.columnEnd >= column)
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
// Open the class that is associated with the field's owner.
|
||||
if (!field.owner.equals(container.getName()))
|
||||
{
|
||||
open(textArea, false, true, false);
|
||||
return;
|
||||
}
|
||||
|
||||
ClassFieldLocation first = fields.get(0);
|
||||
int startOffset = root.getElement(first.line - 1).getStartOffset() + (first.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
}
|
||||
}));
|
||||
|
||||
container.methodParameterMembers.values().forEach(parameters -> parameters.forEach(parameter -> {
|
||||
if (parameter.line == line && parameter.columnStart - 1 <= column && parameter.columnEnd >= column)
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
if (parameter.decRef.equalsIgnoreCase("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(parameter.line - 1).getStartOffset() + (parameter.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
} else
|
||||
{
|
||||
String method = parameter.method;
|
||||
parameters.stream().filter(classParameterLocation -> classParameterLocation.method.equals(method)).forEach(classParameterLocation -> {
|
||||
if (classParameterLocation.decRef.equalsIgnoreCase("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(classParameterLocation.line - 1).getStartOffset() + (classParameterLocation.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
container.methodLocalMembers.values().forEach(localMembers -> localMembers.forEach(localMember -> {
|
||||
if (localMember.line == line && localMember.columnStart - 1 <= column && localMember.columnEnd >= column)
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
if (localMember.decRef.equals("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(localMember.line - 1).getStartOffset() + (localMember.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
} else
|
||||
{
|
||||
String method = localMember.method;
|
||||
localMembers.stream().filter(classLocalVariableLocation -> classLocalVariableLocation.method.equals(method)).forEach(classLocalVariableLocation -> {
|
||||
if (classLocalVariableLocation.decRef.equalsIgnoreCase("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(classLocalVariableLocation.line - 1).getStartOffset() + (classLocalVariableLocation.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
container.methodMembers.values().forEach(methods -> methods.forEach(method -> {
|
||||
if (method.line == line && method.columnStart - 1 <= column && method.columnEnd >= column)
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
if (method.decRef.equalsIgnoreCase("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(method.line - 1).getStartOffset() + (method.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
} else
|
||||
{
|
||||
methods.stream().filter(classMethodLocation -> classMethodLocation.owner.equals(method.owner)).forEach(classMethodLocation -> {
|
||||
if (classMethodLocation.decRef.equalsIgnoreCase("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(classMethodLocation.line - 1).getStartOffset() + (classMethodLocation.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
open(textArea, false, false, true);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
container.classReferences.values().forEach(classes -> classes.forEach(clazz -> {
|
||||
String name;
|
||||
if (clazz.line == line && clazz.columnStart - 1 <= column && clazz.columnEnd - 1 >= column)
|
||||
{
|
||||
name = clazz.owner;
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
if (clazz.type.equals("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(clazz.line - 1).getStartOffset() + (clazz.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
} else
|
||||
{
|
||||
classes.stream().filter(classReferenceLocation -> classReferenceLocation.owner.equals(name)).forEach(classReferenceLocation -> {
|
||||
if (classReferenceLocation.type.equals("declaration"))
|
||||
{
|
||||
int startOffset = root.getElement(classReferenceLocation.line - 1).getStartOffset() + (classReferenceLocation.columnStart - 1);
|
||||
textArea.setCaretPosition(startOffset);
|
||||
}
|
||||
});
|
||||
|
||||
// Should not really do anything when the class is already open
|
||||
open(textArea, true, false, false);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private ClassFileContainer openClass(String lexeme, boolean field, boolean method)
|
||||
{
|
||||
if (lexeme.equals(container.getName()))
|
||||
return null;
|
||||
|
||||
ResourceContainer resourceContainer = BytecodeViewer.getFileContainer(container.getParentContainer());
|
||||
if (resourceContainer == null)
|
||||
return null;
|
||||
|
||||
if (field)
|
||||
{
|
||||
String className = container.getClassForField(lexeme);
|
||||
BytecodeViewer.viewer.workPane.addClassResource(resourceContainer, className + ".class");
|
||||
ClassViewer activeResource = (ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource();
|
||||
HashMap<String, ClassFileContainer> classFiles = BytecodeViewer.viewer.workPane.classFiles;
|
||||
return wait(classFiles, activeResource);
|
||||
} else if (method)
|
||||
{
|
||||
ClassMethodLocation classMethodLocation = container.getMethodLocationsFor(lexeme).get(0);
|
||||
ClassReferenceLocation classReferenceLocation = null;
|
||||
try
|
||||
{
|
||||
classReferenceLocation = container.getClassReferenceLocationsFor(classMethodLocation.owner).get(0);
|
||||
} catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
if (classReferenceLocation == null)
|
||||
return null;
|
||||
|
||||
String packagePath = classReferenceLocation.packagePath;
|
||||
if (packagePath.startsWith("java") || packagePath.startsWith("javax") || packagePath.startsWith("com.sun"))
|
||||
return null;
|
||||
|
||||
String resourceName = packagePath + "/" + classMethodLocation.owner;
|
||||
if (resourceContainer.resourceClasses.containsKey(resourceName))
|
||||
{
|
||||
BytecodeViewer.viewer.workPane.addClassResource(resourceContainer, resourceName + ".class");
|
||||
ClassViewer activeResource = (ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource();
|
||||
HashMap<String, ClassFileContainer> classFiles = BytecodeViewer.viewer.workPane.classFiles;
|
||||
return wait(classFiles, activeResource);
|
||||
}
|
||||
} else
|
||||
{
|
||||
ClassReferenceLocation classReferenceLocation = container.getClassReferenceLocationsFor(lexeme).get(0);
|
||||
String packagePath = classReferenceLocation.packagePath;
|
||||
if (packagePath.startsWith("java") || packagePath.startsWith("javax") || packagePath.startsWith("com.sun"))
|
||||
return null;
|
||||
|
||||
String resourceName = packagePath + "/" + lexeme;
|
||||
if (resourceContainer.resourceClasses.containsKey(resourceName))
|
||||
{
|
||||
BytecodeViewer.viewer.workPane.addClassResource(resourceContainer, resourceName + ".class");
|
||||
ClassViewer activeResource = (ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource();
|
||||
HashMap<String, ClassFileContainer> classFiles = BytecodeViewer.viewer.workPane.classFiles;
|
||||
return wait(classFiles, activeResource);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void open(RSyntaxTextArea textArea, boolean isClass, boolean isField, boolean isMethod)
|
||||
{
|
||||
Thread thread = new Thread(() -> {
|
||||
Token token = textArea.modelToToken(textArea.getCaretPosition());
|
||||
token = TokenUtil.getToken(textArea, token);
|
||||
String lexeme = token.getLexeme();
|
||||
ClassFileContainer classFileContainer;
|
||||
if (isClass)
|
||||
{
|
||||
classFileContainer = openClass(lexeme, false, false);
|
||||
if (classFileContainer == null)
|
||||
return;
|
||||
|
||||
classFileContainer.classReferences.forEach((className, classReference) -> {
|
||||
if (className.equals(lexeme))
|
||||
{
|
||||
classReference.forEach(classReferenceLocation -> {
|
||||
if (classReferenceLocation.type.equals("declaration"))
|
||||
{
|
||||
moveCursor(classReferenceLocation.line, classReferenceLocation.columnStart);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (isField)
|
||||
{
|
||||
classFileContainer = openClass(lexeme, true, false);
|
||||
if (classFileContainer == null)
|
||||
return;
|
||||
|
||||
classFileContainer.fieldMembers.forEach((fieldName, fields) -> {
|
||||
if (fieldName.equals(lexeme))
|
||||
{
|
||||
fields.forEach(classFieldLocation -> {
|
||||
if (classFieldLocation.type.equals("declaration"))
|
||||
{
|
||||
moveCursor(classFieldLocation.line, classFieldLocation.columnStart);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (isMethod)
|
||||
{
|
||||
classFileContainer = openClass(lexeme, false, true);
|
||||
if (classFileContainer == null)
|
||||
return;
|
||||
|
||||
classFileContainer.methodMembers.forEach((methodName, methods) -> {
|
||||
if (methodName.equals(lexeme))
|
||||
{
|
||||
methods.forEach(method -> {
|
||||
if (method.decRef.equalsIgnoreCase("declaration"))
|
||||
{
|
||||
moveCursor(method.line, method.columnStart);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, "Open Class");
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private ClassFileContainer wait(HashMap<String, ClassFileContainer> classFiles, ClassViewer activeResource)
|
||||
{
|
||||
String containerName = activeResource.resource.workingName + "-" + this.container.getDecompiler();
|
||||
try
|
||||
{
|
||||
BytecodeViewer.updateBusyStatus(true);
|
||||
Thread.getAllStackTraces().forEach((name, stackTrace) -> {
|
||||
if (name.getName().equals("Pane Update"))
|
||||
{
|
||||
try
|
||||
{
|
||||
name.join();
|
||||
} catch (InterruptedException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
} finally
|
||||
{
|
||||
BytecodeViewer.updateBusyStatus(false);
|
||||
}
|
||||
|
||||
return classFiles.get(containerName);
|
||||
}
|
||||
|
||||
private void moveCursor(int line, int columnStart)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
BytecodeViewPanel panel = ((ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource()).getPanel(i);
|
||||
if (panel.decompiler.getDecompilerName().equals(this.container.getDecompiler()))
|
||||
{
|
||||
Element root = panel.textArea.getDocument().getDefaultRootElement();
|
||||
int startOffset = root.getElement(line - 1).getStartOffset() + (columnStart - 1);
|
||||
panel.textArea.setCaretPosition(startOffset);
|
||||
for (CaretListener caretListener : panel.textArea.getCaretListeners())
|
||||
{
|
||||
if (caretListener instanceof BytecodeViewPanelUpdater.MarkerCaretListener)
|
||||
{
|
||||
BytecodeViewPanelUpdater.MarkerCaretListener markerCaretListener = (BytecodeViewPanelUpdater.MarkerCaretListener) caretListener;
|
||||
markerCaretListener.caretUpdate(new CaretEvent(panel.textArea)
|
||||
{
|
||||
@Override
|
||||
public int getDot()
|
||||
{
|
||||
return panel.textArea.getCaret().getDot();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMark()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
panel.textArea.requestFocusInWindow();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ClassViewer;
|
|||
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.FileViewer;
|
||||
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ResourceViewer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.ResourceContainer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.ClassFileContainer;
|
||||
import the.bytecode.club.bytecodeviewer.translation.TranslatedComponents;
|
||||
import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings;
|
||||
import the.bytecode.club.bytecodeviewer.translation.components.TranslatedJButton;
|
||||
|
@ -33,13 +34,10 @@ import the.bytecode.club.uikit.tabpopup.closer.PopupMenuTabsCloseConfiguration;
|
|||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static the.bytecode.club.bytecodeviewer.Constants.BLOCK_TAB_MENU;
|
||||
|
||||
/**
|
||||
* This pane contains all the resources, as tabs.
|
||||
*
|
||||
|
@ -54,6 +52,7 @@ public class Workspace extends TranslatedVisibleComponent {
|
|||
public final JPanel buttonPanel;
|
||||
public final JButton refreshClass;
|
||||
public final Set<String> openedTabs = new HashSet<>();
|
||||
public HashMap<String, ClassFileContainer> classFiles = new HashMap<>();
|
||||
|
||||
public Workspace() {
|
||||
super("Workspace", TranslatedComponents.WORK_SPACE);
|
||||
|
|
|
@ -51,306 +51,316 @@ import static the.bytecode.club.bytecodeviewer.util.MethodParser.Method;
|
|||
|
||||
public class ClassViewer extends ResourceViewer
|
||||
{
|
||||
public JSplitPane sp;
|
||||
public JSplitPane sp2;
|
||||
public BytecodeViewPanel bytecodeViewPanel1 = new BytecodeViewPanel(0, this);
|
||||
public BytecodeViewPanel bytecodeViewPanel2 = new BytecodeViewPanel(1, this);
|
||||
public BytecodeViewPanel bytecodeViewPanel3 = new BytecodeViewPanel(2, this);
|
||||
public List<MethodParser> methods = Arrays.asList(new MethodParser(), new MethodParser(), new MethodParser());
|
||||
|
||||
public ClassViewer(ResourceContainer container, String name)
|
||||
{
|
||||
super(new Resource(name, container.getWorkingName(name), container));
|
||||
|
||||
this.setName(name);
|
||||
this.setLayout(new BorderLayout());
|
||||
this.sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, bytecodeViewPanel1, bytecodeViewPanel2);
|
||||
this.sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp, bytecodeViewPanel3);
|
||||
this.add(sp2, BorderLayout.CENTER);
|
||||
|
||||
this.addComponentListener(new ComponentAdapter()
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e)
|
||||
{
|
||||
resetDivider();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(JButton button)
|
||||
{
|
||||
setPanes();
|
||||
refreshTitle();
|
||||
|
||||
bytecodeViewPanel1.createPane(this);
|
||||
bytecodeViewPanel2.createPane(this);
|
||||
bytecodeViewPanel3.createPane(this);
|
||||
|
||||
byte[] classBytes = getResourceBytes();
|
||||
|
||||
//TODO remove this once all of the importers have been properly updated to use a FileContainerImporter
|
||||
if (classBytes == null || classBytes.length == 0 || Configuration.forceResourceUpdateFromClassNode)
|
||||
{
|
||||
//TODO remove this error message when all of the importers have been updated
|
||||
// only APK and DEX are left
|
||||
if (!Configuration.forceResourceUpdateFromClassNode)
|
||||
{
|
||||
System.err.println("WARNING: Class Resource imported using the old importer!");
|
||||
System.err.println("TODO: Update it to use the FileContainerImporter");
|
||||
}
|
||||
|
||||
classBytes = ASMUtil.nodeToBytes(resource.getResourceClassNode());
|
||||
}
|
||||
|
||||
bytecodeViewPanel1.updatePane(this, classBytes, button, isPanel1Editable());
|
||||
bytecodeViewPanel2.updatePane(this, classBytes, button, isPanel2Editable());
|
||||
bytecodeViewPanel3.updatePane(this, classBytes, button, isPanel3Editable());
|
||||
|
||||
Thread dumpBuild = new Thread(() ->
|
||||
{
|
||||
BytecodeViewer.updateBusyStatus(true);
|
||||
|
||||
while (Configuration.currentlyDumping)
|
||||
{
|
||||
//wait until it's not dumping
|
||||
try
|
||||
{
|
||||
Thread.sleep(100);
|
||||
}
|
||||
catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
BytecodeViewer.updateBusyStatus(false);
|
||||
|
||||
if (bytecodeViewPanel1.decompiler != Decompiler.NONE)
|
||||
bytecodeViewPanel1.updateThread.startNewThread();
|
||||
|
||||
if (bytecodeViewPanel2.decompiler != Decompiler.NONE)
|
||||
bytecodeViewPanel2.updateThread.startNewThread();
|
||||
|
||||
if (bytecodeViewPanel3.decompiler != Decompiler.NONE)
|
||||
bytecodeViewPanel3.updateThread.startNewThread();
|
||||
}, "ClassViewer Temp Dump");
|
||||
|
||||
dumpBuild.start();
|
||||
|
||||
if (isPanel1Editable() || isPanel2Editable() || isPanel3Editable())
|
||||
{
|
||||
if (Configuration.warnForEditing)
|
||||
return;
|
||||
|
||||
Configuration.warnForEditing = true;
|
||||
|
||||
if (!BytecodeViewer.viewer.autoCompileOnRefresh.isSelected() && !BytecodeViewer.viewer.compileOnSave.isSelected())
|
||||
{
|
||||
BytecodeViewer.showMessage("Make sure to compile (File>Compile or Ctrl + T) whenever you want to " + "test or export your changes.\nYou can set compile automatically on refresh or on save " + "in the settings menu.");
|
||||
|
||||
SettingsSerializer.saveSettingsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPanes()
|
||||
{
|
||||
bytecodeViewPanel1.decompiler = BytecodeViewer.viewer.viewPane1.getSelectedDecompiler();
|
||||
bytecodeViewPanel2.decompiler = BytecodeViewer.viewer.viewPane2.getSelectedDecompiler();
|
||||
bytecodeViewPanel3.decompiler = BytecodeViewer.viewer.viewPane3.getSelectedDecompiler();
|
||||
}
|
||||
|
||||
public boolean isPanel1Editable()
|
||||
{
|
||||
setPanes();
|
||||
return BytecodeViewer.viewer.viewPane1.isPaneEditable();
|
||||
}
|
||||
|
||||
public boolean isPanel2Editable()
|
||||
{
|
||||
setPanes();
|
||||
return BytecodeViewer.viewer.viewPane2.isPaneEditable();
|
||||
}
|
||||
|
||||
public boolean isPanel3Editable()
|
||||
{
|
||||
setPanes();
|
||||
return BytecodeViewer.viewer.viewPane3.isPaneEditable();
|
||||
}
|
||||
|
||||
|
||||
public static void selectMethod(RSyntaxTextArea area, int methodLine)
|
||||
{
|
||||
if (methodLine != area.getCaretLineNumber())
|
||||
{
|
||||
setCaretLine(area, methodLine);
|
||||
setViewLine(area, methodLine);
|
||||
}
|
||||
}
|
||||
|
||||
public static void selectMethod(ClassViewer classViewer, int paneId, Method method)
|
||||
{
|
||||
RSyntaxTextArea area = null;
|
||||
switch (paneId)
|
||||
{
|
||||
case 0:
|
||||
area = classViewer.bytecodeViewPanel1.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
area = classViewer.bytecodeViewPanel2.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
area = classViewer.bytecodeViewPanel3.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
}
|
||||
|
||||
if (area != null)
|
||||
{
|
||||
MethodParser methods = classViewer.methods.get(paneId);
|
||||
if (methods != null)
|
||||
{
|
||||
int methodLine = methods.findMethod(method);
|
||||
|
||||
if (methodLine != -1)
|
||||
selectMethod(area, methodLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMaxViewLine(RSyntaxTextArea area)
|
||||
{
|
||||
Container parent = area.getParent();
|
||||
|
||||
if (parent instanceof JViewport)
|
||||
{
|
||||
JViewport viewport = (JViewport) parent;
|
||||
int y = viewport.getViewSize().height - viewport.getExtentSize().height;
|
||||
int lineHeight = area.getLineHeight();
|
||||
return y >= lineHeight ? y / lineHeight : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int getViewLine(RSyntaxTextArea area)
|
||||
{
|
||||
Container parent = area.getParent();
|
||||
|
||||
if (parent instanceof JViewport)
|
||||
{
|
||||
JViewport viewport = (JViewport) parent;
|
||||
Point point = viewport.getViewPosition();
|
||||
int lineHeight = area.getLineHeight();
|
||||
return point.y >= lineHeight ? point.y / lineHeight : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void setViewLine(RSyntaxTextArea area, int line)
|
||||
{
|
||||
Container parent = area.getParent();
|
||||
|
||||
if (parent instanceof JViewport)
|
||||
{
|
||||
JViewport viewport = (JViewport) parent;
|
||||
int maxLine = ClassViewer.getMaxViewLine(area);
|
||||
line = Math.min(line, maxLine);
|
||||
viewport.setViewPosition(new Point(0, line * area.getLineHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCaretLine(RSyntaxTextArea area, int line)
|
||||
{
|
||||
try
|
||||
{
|
||||
area.setCaretPosition(area.getLineStartOffset(line));
|
||||
}
|
||||
catch (BadLocationException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void resetDivider()
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
sp.setResizeWeight(0.5);
|
||||
|
||||
if (bytecodeViewPanel2.decompiler != Decompiler.NONE && bytecodeViewPanel1.decompiler != Decompiler.NONE)
|
||||
setDividerLocation(sp, 0.5);
|
||||
else if (bytecodeViewPanel1.decompiler != Decompiler.NONE)
|
||||
setDividerLocation(sp, 1);
|
||||
else if (bytecodeViewPanel2.decompiler != Decompiler.NONE)
|
||||
{
|
||||
sp.setResizeWeight(1);
|
||||
setDividerLocation(sp, 0);
|
||||
}
|
||||
else
|
||||
setDividerLocation(sp, 0);
|
||||
|
||||
if (bytecodeViewPanel3.decompiler != Decompiler.NONE)
|
||||
{
|
||||
sp2.setResizeWeight(0.7);
|
||||
setDividerLocation(sp2, 0.7);
|
||||
if ((bytecodeViewPanel2.decompiler == Decompiler.NONE && bytecodeViewPanel1.decompiler != Decompiler.NONE) || (bytecodeViewPanel1.decompiler == Decompiler.NONE && bytecodeViewPanel2.decompiler != Decompiler.NONE))
|
||||
setDividerLocation(sp2, 0.5);
|
||||
else if (bytecodeViewPanel1.decompiler == Decompiler.NONE)
|
||||
setDividerLocation(sp2, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
sp.setResizeWeight(1);
|
||||
sp2.setResizeWeight(0);
|
||||
setDividerLocation(sp2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whoever wrote this function, THANK YOU!
|
||||
*/
|
||||
public static JSplitPane setDividerLocation(JSplitPane splitter, double proportion)
|
||||
{
|
||||
if (splitter.isShowing())
|
||||
{
|
||||
if (splitter.getWidth() > 0 && splitter.getHeight() > 0)
|
||||
splitter.setDividerLocation(proportion);
|
||||
else
|
||||
{
|
||||
splitter.addComponentListener(new ComponentAdapter()
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent ce)
|
||||
{
|
||||
splitter.removeComponentListener(this);
|
||||
setDividerLocation(splitter, proportion);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
splitter.addHierarchyListener(new HierarchyListener()
|
||||
{
|
||||
@Override
|
||||
public void hierarchyChanged(HierarchyEvent e)
|
||||
{
|
||||
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && splitter.isShowing())
|
||||
{
|
||||
splitter.removeHierarchyListener(this);
|
||||
setDividerLocation(splitter, proportion);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return splitter;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -8650495368920680024L;
|
||||
public JSplitPane sp;
|
||||
public JSplitPane sp2;
|
||||
public BytecodeViewPanel bytecodeViewPanel1 = new BytecodeViewPanel(0, this);
|
||||
public BytecodeViewPanel bytecodeViewPanel2 = new BytecodeViewPanel(1, this);
|
||||
public BytecodeViewPanel bytecodeViewPanel3 = new BytecodeViewPanel(2, this);
|
||||
public List<MethodParser> methods = Arrays.asList(new MethodParser(), new MethodParser(), new MethodParser());
|
||||
|
||||
public ClassViewer(ResourceContainer container, String name)
|
||||
{
|
||||
super(new Resource(name, container.getWorkingName(name), container));
|
||||
|
||||
this.setName(name);
|
||||
this.setLayout(new BorderLayout());
|
||||
this.sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, bytecodeViewPanel1, bytecodeViewPanel2);
|
||||
this.sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp, bytecodeViewPanel3);
|
||||
this.add(sp2, BorderLayout.CENTER);
|
||||
|
||||
this.addComponentListener(new ComponentAdapter()
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent e)
|
||||
{
|
||||
resetDivider();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(JButton button)
|
||||
{
|
||||
setPanes();
|
||||
refreshTitle();
|
||||
|
||||
bytecodeViewPanel1.createPane(this);
|
||||
bytecodeViewPanel2.createPane(this);
|
||||
bytecodeViewPanel3.createPane(this);
|
||||
|
||||
byte[] classBytes = getResourceBytes();
|
||||
|
||||
//TODO remove this once all of the importers have been properly updated to use a FileContainerImporter
|
||||
if (classBytes == null || classBytes.length == 0 || Configuration.forceResourceUpdateFromClassNode)
|
||||
{
|
||||
//TODO remove this error message when all of the importers have been updated
|
||||
// only APK and DEX are left
|
||||
if (!Configuration.forceResourceUpdateFromClassNode)
|
||||
{
|
||||
System.err.println("WARNING: Class Resource imported using the old importer!");
|
||||
System.err.println("TODO: Update it to use the FileContainerImporter");
|
||||
}
|
||||
|
||||
classBytes = ASMUtil.nodeToBytes(resource.getResourceClassNode());
|
||||
}
|
||||
|
||||
bytecodeViewPanel1.updatePane(this, classBytes, button, isPanel1Editable());
|
||||
bytecodeViewPanel2.updatePane(this, classBytes, button, isPanel2Editable());
|
||||
bytecodeViewPanel3.updatePane(this, classBytes, button, isPanel3Editable());
|
||||
|
||||
Thread dumpBuild = new Thread(() ->
|
||||
{
|
||||
BytecodeViewer.updateBusyStatus(true);
|
||||
|
||||
while (Configuration.currentlyDumping)
|
||||
{
|
||||
//wait until it's not dumping
|
||||
try
|
||||
{
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
BytecodeViewer.updateBusyStatus(false);
|
||||
|
||||
if (bytecodeViewPanel1.decompiler != Decompiler.NONE)
|
||||
bytecodeViewPanel1.updateThread.startNewThread();
|
||||
|
||||
if (bytecodeViewPanel2.decompiler != Decompiler.NONE)
|
||||
bytecodeViewPanel2.updateThread.startNewThread();
|
||||
|
||||
if (bytecodeViewPanel3.decompiler != Decompiler.NONE)
|
||||
bytecodeViewPanel3.updateThread.startNewThread();
|
||||
}, "ClassViewer Temp Dump");
|
||||
|
||||
dumpBuild.start();
|
||||
|
||||
if (isPanel1Editable() || isPanel2Editable() || isPanel3Editable())
|
||||
{
|
||||
if (Configuration.warnForEditing)
|
||||
return;
|
||||
|
||||
Configuration.warnForEditing = true;
|
||||
|
||||
if (!BytecodeViewer.viewer.autoCompileOnRefresh.isSelected() && !BytecodeViewer.viewer.compileOnSave.isSelected())
|
||||
{
|
||||
BytecodeViewer.showMessage("Make sure to compile (File>Compile or Ctrl + T) whenever you want to " + "test or export your changes.\nYou can set compile automatically on refresh or on save " + "in the settings menu.");
|
||||
|
||||
SettingsSerializer.saveSettingsAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setPanes()
|
||||
{
|
||||
bytecodeViewPanel1.decompiler = BytecodeViewer.viewer.viewPane1.getSelectedDecompiler();
|
||||
bytecodeViewPanel2.decompiler = BytecodeViewer.viewer.viewPane2.getSelectedDecompiler();
|
||||
bytecodeViewPanel3.decompiler = BytecodeViewer.viewer.viewPane3.getSelectedDecompiler();
|
||||
}
|
||||
|
||||
public boolean isPanel1Editable()
|
||||
{
|
||||
setPanes();
|
||||
return BytecodeViewer.viewer.viewPane1.isPaneEditable();
|
||||
}
|
||||
|
||||
public boolean isPanel2Editable()
|
||||
{
|
||||
setPanes();
|
||||
return BytecodeViewer.viewer.viewPane2.isPaneEditable();
|
||||
}
|
||||
|
||||
public boolean isPanel3Editable()
|
||||
{
|
||||
setPanes();
|
||||
return BytecodeViewer.viewer.viewPane3.isPaneEditable();
|
||||
}
|
||||
|
||||
public BytecodeViewPanel getPanel(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return bytecodeViewPanel1;
|
||||
case 1:
|
||||
return bytecodeViewPanel2;
|
||||
case 2:
|
||||
return bytecodeViewPanel3;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public static void selectMethod(RSyntaxTextArea area, int methodLine)
|
||||
{
|
||||
if (methodLine != area.getCaretLineNumber())
|
||||
{
|
||||
setCaretLine(area, methodLine);
|
||||
setViewLine(area, methodLine);
|
||||
}
|
||||
}
|
||||
|
||||
public static void selectMethod(ClassViewer classViewer, int paneId, Method method)
|
||||
{
|
||||
RSyntaxTextArea area = null;
|
||||
switch (paneId)
|
||||
{
|
||||
case 0:
|
||||
area = classViewer.bytecodeViewPanel1.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
area = classViewer.bytecodeViewPanel2.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
area = classViewer.bytecodeViewPanel3.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
}
|
||||
|
||||
if (area != null)
|
||||
{
|
||||
MethodParser methods = classViewer.methods.get(paneId);
|
||||
if (methods != null)
|
||||
{
|
||||
int methodLine = methods.findMethod(method);
|
||||
|
||||
if (methodLine != -1)
|
||||
selectMethod(area, methodLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMaxViewLine(RSyntaxTextArea area)
|
||||
{
|
||||
Container parent = area.getParent();
|
||||
|
||||
if (parent instanceof JViewport)
|
||||
{
|
||||
JViewport viewport = (JViewport) parent;
|
||||
int y = viewport.getViewSize().height - viewport.getExtentSize().height;
|
||||
int lineHeight = area.getLineHeight();
|
||||
return y >= lineHeight ? y / lineHeight : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static int getViewLine(RSyntaxTextArea area)
|
||||
{
|
||||
Container parent = area.getParent();
|
||||
|
||||
if (parent instanceof JViewport)
|
||||
{
|
||||
JViewport viewport = (JViewport) parent;
|
||||
Point point = viewport.getViewPosition();
|
||||
int lineHeight = area.getLineHeight();
|
||||
return point.y >= lineHeight ? point.y / lineHeight : 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void setViewLine(RSyntaxTextArea area, int line)
|
||||
{
|
||||
Container parent = area.getParent();
|
||||
|
||||
if (parent instanceof JViewport)
|
||||
{
|
||||
JViewport viewport = (JViewport) parent;
|
||||
int maxLine = ClassViewer.getMaxViewLine(area);
|
||||
line = Math.min(line, maxLine);
|
||||
viewport.setViewPosition(new Point(0, line * area.getLineHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCaretLine(RSyntaxTextArea area, int line)
|
||||
{
|
||||
try
|
||||
{
|
||||
area.setCaretPosition(area.getLineStartOffset(line));
|
||||
} catch (BadLocationException ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void resetDivider()
|
||||
{
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
sp.setResizeWeight(0.5);
|
||||
|
||||
if (bytecodeViewPanel2.decompiler != Decompiler.NONE && bytecodeViewPanel1.decompiler != Decompiler.NONE)
|
||||
setDividerLocation(sp, 0.5);
|
||||
else if (bytecodeViewPanel1.decompiler != Decompiler.NONE)
|
||||
setDividerLocation(sp, 1);
|
||||
else if (bytecodeViewPanel2.decompiler != Decompiler.NONE)
|
||||
{
|
||||
sp.setResizeWeight(1);
|
||||
setDividerLocation(sp, 0);
|
||||
} else
|
||||
setDividerLocation(sp, 0);
|
||||
|
||||
if (bytecodeViewPanel3.decompiler != Decompiler.NONE)
|
||||
{
|
||||
sp2.setResizeWeight(0.7);
|
||||
setDividerLocation(sp2, 0.7);
|
||||
if ((bytecodeViewPanel2.decompiler == Decompiler.NONE && bytecodeViewPanel1.decompiler != Decompiler.NONE) || (bytecodeViewPanel1.decompiler == Decompiler.NONE && bytecodeViewPanel2.decompiler != Decompiler.NONE))
|
||||
setDividerLocation(sp2, 0.5);
|
||||
else if (bytecodeViewPanel1.decompiler == Decompiler.NONE)
|
||||
setDividerLocation(sp2, 0);
|
||||
} else
|
||||
{
|
||||
sp.setResizeWeight(1);
|
||||
sp2.setResizeWeight(0);
|
||||
setDividerLocation(sp2, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whoever wrote this function, THANK YOU!
|
||||
*/
|
||||
public static JSplitPane setDividerLocation(JSplitPane splitter, double proportion)
|
||||
{
|
||||
if (splitter.isShowing())
|
||||
{
|
||||
if (splitter.getWidth() > 0 && splitter.getHeight() > 0)
|
||||
splitter.setDividerLocation(proportion);
|
||||
else
|
||||
{
|
||||
splitter.addComponentListener(new ComponentAdapter()
|
||||
{
|
||||
@Override
|
||||
public void componentResized(ComponentEvent ce)
|
||||
{
|
||||
splitter.removeComponentListener(this);
|
||||
setDividerLocation(splitter, proportion);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else
|
||||
{
|
||||
splitter.addHierarchyListener(new HierarchyListener()
|
||||
{
|
||||
@Override
|
||||
public void hierarchyChanged(HierarchyEvent e)
|
||||
{
|
||||
if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0 && splitter.isShowing())
|
||||
{
|
||||
splitter.removeHierarchyListener(this);
|
||||
setDividerLocation(splitter, proportion);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return splitter;
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -8650495368920680024L;
|
||||
}
|
||||
|
|
|
@ -18,35 +18,38 @@
|
|||
|
||||
package the.bytecode.club.bytecodeviewer.gui.util;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JComboBox;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JViewport;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
|
||||
import org.fife.ui.rsyntaxtextarea.TokenMakerFactory;
|
||||
import org.fife.ui.rsyntaxtextarea.*;
|
||||
import org.fife.ui.rtextarea.SmartHighlightPainter;
|
||||
import org.objectweb.asm.ClassWriter;
|
||||
import the.bytecode.club.bytecodeviewer.BytecodeViewer;
|
||||
import the.bytecode.club.bytecodeviewer.Configuration;
|
||||
import the.bytecode.club.bytecodeviewer.compilers.Compiler;
|
||||
import the.bytecode.club.bytecodeviewer.decompilers.Decompiler;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.MethodsRenderer;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.MyErrorStripe;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.RSyntaxTextAreaHighlighterEx;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.SearchableRSyntaxTextArea;
|
||||
import the.bytecode.club.bytecodeviewer.gui.components.actions.GoToAction;
|
||||
import the.bytecode.club.bytecodeviewer.gui.hexviewer.HexViewer;
|
||||
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.BytecodeViewPanel;
|
||||
import the.bytecode.club.bytecodeviewer.gui.resourceviewer.viewer.ClassViewer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.ClassFileContainer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.*;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.parser.TokenUtil;
|
||||
import the.bytecode.club.bytecodeviewer.util.MethodParser;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.CaretEvent;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.event.ChangeEvent;
|
||||
import javax.swing.event.ChangeListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Element;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import static the.bytecode.club.bytecodeviewer.gui.resourceviewer.TabbedPane.BLANK_COLOR;
|
||||
import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.EDITABLE;
|
||||
|
||||
|
@ -65,14 +68,15 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
public final BytecodeViewPanel bytecodeViewPanel;
|
||||
private final JButton button;
|
||||
private final byte[] classBytes;
|
||||
|
||||
|
||||
|
||||
public MarkerCaretListener markerCaretListener;
|
||||
private MyErrorStripe errorStripe;
|
||||
public SearchableRSyntaxTextArea updateUpdaterTextArea;
|
||||
public JComboBox<Integer> methodsList;
|
||||
public boolean isPanelEditable;
|
||||
public boolean waitingFor;
|
||||
private Thread thread;
|
||||
|
||||
|
||||
public BytecodeViewPanelUpdater(BytecodeViewPanel bytecodeViewPanel, ClassViewer cv, byte[] classBytes, boolean isPanelEditable, JButton button)
|
||||
{
|
||||
this.viewer = cv;
|
||||
|
@ -82,13 +86,13 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
this.button = button;
|
||||
waitingFor = true;
|
||||
}
|
||||
|
||||
|
||||
public void processDisplay()
|
||||
{
|
||||
try
|
||||
{
|
||||
BytecodeViewer.updateBusyStatus(true);
|
||||
|
||||
|
||||
if (bytecodeViewPanel.decompiler != Decompiler.NONE)
|
||||
{
|
||||
//hex viewer
|
||||
|
@ -96,46 +100,53 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
{
|
||||
final ClassWriter cw = new ClassWriter(0);
|
||||
viewer.resource.getResourceClassNode().accept(cw);
|
||||
|
||||
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
final HexViewer hex = new HexViewer(cw.toByteArray());
|
||||
bytecodeViewPanel.add(hex);
|
||||
});
|
||||
}
|
||||
else
|
||||
} else
|
||||
{
|
||||
final Decompiler decompiler = bytecodeViewPanel.decompiler;
|
||||
|
||||
|
||||
//perform decompiling inside of this thread
|
||||
final String decompiledSource = decompiler.getDecompiler().decompileClassNode(viewer.resource.getResourceClassNode(), classBytes);
|
||||
|
||||
|
||||
ClassFileContainer container = new ClassFileContainer(viewer.resource.workingName + "-" + decompiler.getDecompilerName(), decompiledSource, viewer.resource.container);
|
||||
if (!BytecodeViewer.viewer.workPane.classFiles.containsKey(viewer.resource.workingName + "-" + decompiler.getDecompilerName()))
|
||||
{
|
||||
container.parse();
|
||||
BytecodeViewer.viewer.workPane.classFiles.put(viewer.resource.workingName + "-" + decompiler.getDecompilerName(), container);
|
||||
container.hasBeenParsed = true;
|
||||
}
|
||||
|
||||
//set the swing components on the swing thread
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
buildTextArea(decompiler, decompiledSource);
|
||||
waitingFor = false;
|
||||
});
|
||||
|
||||
|
||||
//hold this thread until the swing thread has finished attaching the components
|
||||
while (waitingFor)
|
||||
{
|
||||
try {
|
||||
try
|
||||
{
|
||||
Thread.sleep(1);
|
||||
} catch (Exception ignored) {}
|
||||
} catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IndexOutOfBoundsException | NullPointerException e)
|
||||
} catch (IndexOutOfBoundsException | NullPointerException e)
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
catch (Exception e)
|
||||
} catch (Exception e)
|
||||
{
|
||||
BytecodeViewer.handleException(e);
|
||||
}
|
||||
finally
|
||||
} finally
|
||||
{
|
||||
viewer.resetDivider();
|
||||
BytecodeViewer.updateBusyStatus(false);
|
||||
|
@ -146,7 +157,7 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void startNewThread()
|
||||
{
|
||||
thread = new Thread(this, "Pane Update");
|
||||
|
@ -156,16 +167,16 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
@Override
|
||||
public void run()
|
||||
{
|
||||
if(bytecodeViewPanel.decompiler == Decompiler.NONE)
|
||||
if (bytecodeViewPanel.decompiler == Decompiler.NONE)
|
||||
return;
|
||||
|
||||
|
||||
processDisplay();
|
||||
|
||||
if(bytecodeViewPanel.decompiler == Decompiler.HEXCODE_VIEWER)
|
||||
|
||||
if (bytecodeViewPanel.decompiler == Decompiler.HEXCODE_VIEWER)
|
||||
return;
|
||||
|
||||
|
||||
//nullcheck broken pane
|
||||
if(updateUpdaterTextArea == null || updateUpdaterTextArea.getScrollPane() == null
|
||||
if (updateUpdaterTextArea == null || updateUpdaterTextArea.getScrollPane() == null
|
||||
|| updateUpdaterTextArea.getScrollPane().getViewport() == null)
|
||||
{
|
||||
//build an error message
|
||||
|
@ -173,7 +184,7 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
buildTextArea(bytecodeViewPanel.decompiler, "Critical BCV Error"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//this still freezes the swing UI
|
||||
synchronizePane();
|
||||
}
|
||||
|
@ -187,22 +198,29 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
if (methods != null)
|
||||
{
|
||||
int methodLine = methods.findActiveMethod(updateUpdaterTextArea.getCaretLineNumber());
|
||||
|
||||
if (methodLine != -1) {
|
||||
if (BytecodeViewer.viewer.showClassMethods.isSelected()) {
|
||||
if (methodsList != null) {
|
||||
if (methodLine != (int) Objects.requireNonNull(methodsList.getSelectedItem())) {
|
||||
|
||||
if (methodLine != -1)
|
||||
{
|
||||
if (BytecodeViewer.viewer.showClassMethods.isSelected())
|
||||
{
|
||||
if (methodsList != null)
|
||||
{
|
||||
if (methodLine != (int) Objects.requireNonNull(methodsList.getSelectedItem()))
|
||||
{
|
||||
methodsList.setSelectedItem(methodLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (BytecodeViewer.viewer.synchronizedViewing.isSelected()) {
|
||||
if (BytecodeViewer.viewer.synchronizedViewing.isSelected())
|
||||
{
|
||||
int panes = 2;
|
||||
if (viewer.bytecodeViewPanel3 != null)
|
||||
panes = 3;
|
||||
|
||||
for (int i = 0; i < panes; i++) {
|
||||
if (i != bytecodeViewPanel.panelIndex) {
|
||||
for (int i = 0; i < panes; i++)
|
||||
{
|
||||
if (i != bytecodeViewPanel.panelIndex)
|
||||
{
|
||||
ClassViewer.selectMethod(viewer, i, methods.getMethod(methodLine));
|
||||
}
|
||||
}
|
||||
|
@ -212,15 +230,19 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
}
|
||||
};
|
||||
|
||||
public final ChangeListener viewportListener = new ChangeListener() {
|
||||
public final ChangeListener viewportListener = new ChangeListener()
|
||||
{
|
||||
@Override
|
||||
public void stateChanged(ChangeEvent e) {
|
||||
public void stateChanged(ChangeEvent e)
|
||||
{
|
||||
int panes = 2;
|
||||
if (viewer.bytecodeViewPanel3 != null)
|
||||
panes = 3;
|
||||
|
||||
if (BytecodeViewer.viewer.synchronizedViewing.isSelected()) {
|
||||
if (updateUpdaterTextArea.isShowing() && (updateUpdaterTextArea.hasFocus() || updateUpdaterTextArea.getMousePosition() != null)) {
|
||||
if (BytecodeViewer.viewer.synchronizedViewing.isSelected())
|
||||
{
|
||||
if (updateUpdaterTextArea.isShowing() && (updateUpdaterTextArea.hasFocus() || updateUpdaterTextArea.getMousePosition() != null))
|
||||
{
|
||||
int caretLine = updateUpdaterTextArea.getCaretLineNumber();
|
||||
int maxViewLine = ClassViewer.getMaxViewLine(updateUpdaterTextArea);
|
||||
int activeViewLine = ClassViewer.getViewLine(updateUpdaterTextArea);
|
||||
|
@ -229,47 +251,59 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
int activeLineDelta = -1;
|
||||
MethodParser.Method activeMethod = null;
|
||||
MethodParser activeMethods = viewer.methods.get(bytecodeViewPanel.panelIndex);
|
||||
if (activeMethods != null) {
|
||||
if (activeMethods != null)
|
||||
{
|
||||
int activeMethodLine = activeMethods.findActiveMethod(activeLine);
|
||||
if (activeMethodLine != -1) {
|
||||
if (activeMethodLine != -1)
|
||||
{
|
||||
activeLineDelta = activeLine - activeMethodLine;
|
||||
activeMethod = activeMethods.getMethod(activeMethodLine);
|
||||
ClassViewer.selectMethod(updateUpdaterTextArea, activeMethodLine);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < panes; i++) {
|
||||
if (i != bytecodeViewPanel.panelIndex) {
|
||||
for (int i = 0; i < panes; i++)
|
||||
{
|
||||
if (i != bytecodeViewPanel.panelIndex)
|
||||
{
|
||||
int setLine = -1;
|
||||
|
||||
RSyntaxTextArea area = null;
|
||||
switch (i) {
|
||||
case 0:
|
||||
area = viewer.bytecodeViewPanel1.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
case 1:
|
||||
area = viewer.bytecodeViewPanel2.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
case 2:
|
||||
area = viewer.bytecodeViewPanel3.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
area = viewer.bytecodeViewPanel1.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
case 1:
|
||||
area = viewer.bytecodeViewPanel2.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
case 2:
|
||||
area = viewer.bytecodeViewPanel3.updateThread.updateUpdaterTextArea;
|
||||
break;
|
||||
}
|
||||
|
||||
if (area != null) {
|
||||
if (activeMethod != null && activeLineDelta >= 0) {
|
||||
if (area != null)
|
||||
{
|
||||
if (activeMethod != null && activeLineDelta >= 0)
|
||||
{
|
||||
MethodParser methods = viewer.methods.get(i);
|
||||
if (methods != null) {
|
||||
if (methods != null)
|
||||
{
|
||||
int methodLine = methods.findMethod(activeMethod);
|
||||
if (methodLine != -1) {
|
||||
if (methodLine != -1)
|
||||
{
|
||||
int viewLine = ClassViewer.getViewLine(area);
|
||||
if (activeLineDelta != viewLine - methodLine) {
|
||||
if (activeLineDelta != viewLine - methodLine)
|
||||
{
|
||||
setLine = methodLine + activeLineDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (activeLine != ClassViewer.getViewLine(area)) {
|
||||
} else if (activeLine != ClassViewer.getViewLine(area))
|
||||
{
|
||||
setLine = activeLine;
|
||||
}
|
||||
if (setLine >= 0) {
|
||||
if (setLine >= 0)
|
||||
{
|
||||
ClassViewer.setViewLine(area, setLine);
|
||||
}
|
||||
}
|
||||
|
@ -279,20 +313,20 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
public void synchronizePane()
|
||||
{
|
||||
if(bytecodeViewPanel.decompiler == Decompiler.HEXCODE_VIEWER
|
||||
if (bytecodeViewPanel.decompiler == Decompiler.HEXCODE_VIEWER
|
||||
|| bytecodeViewPanel.decompiler == Decompiler.NONE)
|
||||
return;
|
||||
|
||||
SwingUtilities.invokeLater(()->
|
||||
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
JViewport viewport = updateUpdaterTextArea.getScrollPane().getViewport();
|
||||
viewport.addChangeListener(viewportListener);
|
||||
updateUpdaterTextArea.addCaretListener(caretListener);
|
||||
});
|
||||
|
||||
|
||||
final MethodParser methods = viewer.methods.get(bytecodeViewPanel.panelIndex);
|
||||
for (int i = 0; i < updateUpdaterTextArea.getLineCount(); i++)
|
||||
{
|
||||
|
@ -312,10 +346,10 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
if (!methods.isEmpty())
|
||||
{
|
||||
methodsList = new JComboBox<>();
|
||||
|
||||
|
||||
for (Integer line : methods.getMethodsLines())
|
||||
methodsList.addItem(line);
|
||||
|
||||
|
||||
methodsList.setRenderer(new MethodsRenderer(this));
|
||||
methodsList.addActionListener(e ->
|
||||
{
|
||||
|
@ -343,8 +377,8 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
panel.add(updateUpdaterTextArea.getScrollPane().getColumnHeader().getComponent(0), BorderLayout.NORTH);
|
||||
panel.add(methodsList, BorderLayout.SOUTH);
|
||||
methodsList.setBackground(BLANK_COLOR);
|
||||
|
||||
SwingUtilities.invokeLater(()->
|
||||
|
||||
SwingUtilities.invokeLater(() ->
|
||||
{
|
||||
updateUpdaterTextArea.getScrollPane().getColumnHeader().removeAll();
|
||||
updateUpdaterTextArea.getScrollPane().getColumnHeader().add(panel);
|
||||
|
@ -352,35 +386,318 @@ public class BytecodeViewPanelUpdater implements Runnable
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void buildTextArea(Decompiler decompiler, String decompiledSource)
|
||||
{
|
||||
updateUpdaterTextArea = new SearchableRSyntaxTextArea();
|
||||
|
||||
|
||||
Configuration.rstaTheme.apply(updateUpdaterTextArea);
|
||||
bytecodeViewPanel.add(updateUpdaterTextArea.getTextAreaSearchPanel(), BorderLayout.NORTH);
|
||||
bytecodeViewPanel.add(updateUpdaterTextArea.getScrollPane());
|
||||
bytecodeViewPanel.add(updateUpdaterTextArea.getTitleHeader(), BorderLayout.NORTH);
|
||||
|
||||
|
||||
bytecodeViewPanel.textArea = updateUpdaterTextArea;
|
||||
if (bytecodeViewPanel.decompiler != Decompiler.BYTECODE_DISASSEMBLER) {
|
||||
|
||||
bytecodeViewPanel.textArea.setMarkOccurrencesColor(Color.ORANGE);
|
||||
bytecodeViewPanel.textArea.setHighlighter(new RSyntaxTextAreaHighlighterEx());
|
||||
|
||||
if (bytecodeViewPanel.decompiler != Decompiler.BYTECODE_DISASSEMBLER)
|
||||
{
|
||||
bytecodeViewPanel.textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA);
|
||||
} else {
|
||||
} else
|
||||
{
|
||||
AbstractTokenMakerFactory tokenMakerFactory = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance();
|
||||
tokenMakerFactory.putMapping("text/javaBytecode", "the.bytecode.club.bytecodeviewer.decompilers.bytecode.JavaBytecodeTokenMaker");
|
||||
bytecodeViewPanel.textArea.setSyntaxEditingStyle("text/javaBytecode");
|
||||
}
|
||||
bytecodeViewPanel.textArea.setCodeFoldingEnabled(true);
|
||||
bytecodeViewPanel.textArea.setAntiAliasingEnabled(true);
|
||||
bytecodeViewPanel.textArea.setText(decompiledSource);
|
||||
bytecodeViewPanel.textArea.setCaretPosition(0);
|
||||
bytecodeViewPanel.textArea.setEditable(isPanelEditable);
|
||||
|
||||
if(isPanelEditable && decompiler == Decompiler.SMALI_DISASSEMBLER)
|
||||
|
||||
if (isPanelEditable && decompiler == Decompiler.SMALI_DISASSEMBLER)
|
||||
bytecodeViewPanel.compiler = Compiler.SMALI_ASSEMBLER;
|
||||
else if(isPanelEditable && decompiler == Decompiler.KRAKATAU_DISASSEMBLER)
|
||||
else if (isPanelEditable && decompiler == Decompiler.KRAKATAU_DISASSEMBLER)
|
||||
bytecodeViewPanel.compiler = Compiler.KRAKATAU_ASSEMBLER;
|
||||
|
||||
|
||||
String editable = isPanelEditable ? " - " + EDITABLE : "";
|
||||
bytecodeViewPanel.textArea.getTitleHeader().setText(decompiler.getDecompilerName() + editable);
|
||||
bytecodeViewPanel.textArea.getTextAreaSearchPanel().getTitleHeader().setText(decompiler.getDecompilerName() + editable);
|
||||
|
||||
errorStripe = new MyErrorStripe(bytecodeViewPanel.textArea);
|
||||
bytecodeViewPanel.add(errorStripe, BorderLayout.LINE_END);
|
||||
|
||||
bytecodeViewPanel.revalidate();
|
||||
bytecodeViewPanel.repaint();
|
||||
|
||||
String classContainerName = viewer.resource.workingName + "-" + decompiler.getDecompilerName();
|
||||
ClassFileContainer classFileContainer = BytecodeViewer.viewer.workPane.classFiles.get(classContainerName);
|
||||
bytecodeViewPanel.textArea.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_DOWN_MASK), "goToAction");
|
||||
bytecodeViewPanel.textArea.getActionMap().put("goToAction", new GoToAction(classFileContainer));
|
||||
|
||||
bytecodeViewPanel.textArea.addMouseMotionListener(new MouseMotionAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e)
|
||||
{
|
||||
if (e.isControlDown())
|
||||
{
|
||||
RSyntaxTextArea textArea = (RSyntaxTextArea) e.getSource();
|
||||
Token token = textArea.viewToToken(e.getPoint());
|
||||
if (token != null)
|
||||
{
|
||||
String lexeme = token.getLexeme();
|
||||
if (classFileContainer.fieldMembers.containsKey(lexeme) || classFileContainer.methodMembers.containsKey(lexeme)
|
||||
|| classFileContainer.methodLocalMembers.containsKey(lexeme) || classFileContainer.methodParameterMembers.containsKey(lexeme)
|
||||
|| classFileContainer.classReferences.containsKey(lexeme))
|
||||
{
|
||||
textArea.setCursor(new Cursor(Cursor.HAND_CURSOR));
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (bytecodeViewPanel.textArea.getCursor().getType() != Cursor.TEXT_CURSOR)
|
||||
{
|
||||
bytecodeViewPanel.textArea.setCursor(new Cursor(Cursor.TEXT_CURSOR));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bytecodeViewPanel.textArea.addMouseListener(new MouseAdapter()
|
||||
{
|
||||
@Override
|
||||
public void mouseClicked(MouseEvent e)
|
||||
{
|
||||
if (e.isControlDown())
|
||||
{
|
||||
RSyntaxTextArea textArea = (RSyntaxTextArea) e.getSource();
|
||||
textArea.getActionMap().get("goToAction").actionPerformed(new ActionEvent(textArea, ActionEvent.ACTION_PERFORMED, "goToAction"));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
markerCaretListener = new MarkerCaretListener(classContainerName);
|
||||
bytecodeViewPanel.textArea.addCaretListener(markerCaretListener);
|
||||
}
|
||||
|
||||
private void markOccurrences(RSyntaxTextArea textArea, ClassFileContainer classFileContainer, MyErrorStripe errorStripe)
|
||||
{
|
||||
RSyntaxTextAreaHighlighterEx highlighterEx = (RSyntaxTextAreaHighlighterEx) textArea.getHighlighter();
|
||||
Token token = textArea.modelToToken(textArea.getCaretPosition());
|
||||
if (token == null)
|
||||
{
|
||||
token = textArea.modelToToken(textArea.getCaretPosition() - 1);
|
||||
if (token == null)
|
||||
{
|
||||
highlighterEx.clearMarkOccurrencesHighlights();
|
||||
errorStripe.refreshMarkers();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
token = TokenUtil.getToken(textArea, token);
|
||||
if (token == null)
|
||||
{
|
||||
highlighterEx.clearMarkOccurrencesHighlights();
|
||||
errorStripe.refreshMarkers();
|
||||
return;
|
||||
}
|
||||
|
||||
int line = textArea.getCaretLineNumber() + 1;
|
||||
int column = textArea.getCaretOffsetFromLineStart();
|
||||
Token finalToken = token;
|
||||
|
||||
/*
|
||||
Fields
|
||||
*/
|
||||
markField(textArea, classFileContainer, line, column, finalToken, highlighterEx);
|
||||
|
||||
/*
|
||||
Methods
|
||||
*/
|
||||
markMethod(textArea, classFileContainer, line, column, finalToken, highlighterEx);
|
||||
|
||||
/*
|
||||
Method parameters
|
||||
*/
|
||||
markMethodParameter(textArea, classFileContainer, line, column, finalToken, highlighterEx);
|
||||
|
||||
/*
|
||||
Method local variables
|
||||
*/
|
||||
markMethodLocalVariable(textArea, classFileContainer, line, column, finalToken, highlighterEx);
|
||||
|
||||
/*
|
||||
Class references
|
||||
*/
|
||||
markClasses(textArea, classFileContainer, line, column, finalToken, highlighterEx);
|
||||
|
||||
errorStripe.refreshMarkers();
|
||||
}
|
||||
|
||||
private void markField(RSyntaxTextArea textArea, ClassFileContainer classFileContainer, int line, int column, Token finalToken, RSyntaxTextAreaHighlighterEx highlighterEx)
|
||||
{
|
||||
classFileContainer.fieldMembers.values().forEach(fields -> fields.forEach(field -> {
|
||||
if (field.line == line && field.columnStart - 1 <= column && field.columnEnd >= column)
|
||||
{
|
||||
try
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
for (ClassFieldLocation location : classFileContainer.getFieldLocationsFor(finalToken.getLexeme()))
|
||||
{
|
||||
int startOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnStart - 1);
|
||||
int endOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnEnd - 1);
|
||||
highlighterEx.addMarkedOccurrenceHighlight(startOffset, endOffset, new SmartHighlightPainter());
|
||||
}
|
||||
} catch (BadLocationException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void markMethod(RSyntaxTextArea textArea, ClassFileContainer classFileContainer, int line, int column, Token finalToken, RSyntaxTextAreaHighlighterEx highlighterEx)
|
||||
{
|
||||
classFileContainer.methodMembers.values().forEach(methods -> methods.forEach(method -> {
|
||||
String owner;
|
||||
String parameters;
|
||||
if (method.line == line && method.columnStart - 1 <= column && method.columnEnd >= column)
|
||||
{
|
||||
owner = method.owner;
|
||||
parameters = method.methodParameterTypes;
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
for (ClassMethodLocation location : classFileContainer.getMethodLocationsFor(finalToken.getLexeme()))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Objects.equals(owner, location.owner) && Objects.equals(parameters, location.methodParameterTypes))
|
||||
{
|
||||
int startOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnStart - 1);
|
||||
int endOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnEnd - 1);
|
||||
highlighterEx.addMarkedOccurrenceHighlight(startOffset, endOffset, new SmartHighlightPainter());
|
||||
}
|
||||
} catch (BadLocationException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through the text area and mark all occurrences that match the selected token.
|
||||
*
|
||||
* @param textArea the text area
|
||||
* @param classFileContainer the container
|
||||
* @param line the caret line
|
||||
* @param column the caret column
|
||||
* @param finalToken the token
|
||||
* @param highlighterEx the highlighter
|
||||
*/
|
||||
private static void markMethodParameter(RSyntaxTextArea textArea, ClassFileContainer classFileContainer, int line, int column, Token finalToken, RSyntaxTextAreaHighlighterEx highlighterEx)
|
||||
{
|
||||
classFileContainer.methodParameterMembers.values().forEach(parameters -> parameters.forEach(parameter -> {
|
||||
String method;
|
||||
if (parameter.line == line && parameter.columnStart - 1 <= column && parameter.columnEnd >= column)
|
||||
{
|
||||
method = parameter.method;
|
||||
try
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
for (ClassParameterLocation location : classFileContainer.getParameterLocationsFor(finalToken.getLexeme()))
|
||||
{
|
||||
if (Objects.equals(method, location.method))
|
||||
{
|
||||
int startOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnStart - 1);
|
||||
int endOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnEnd - 1);
|
||||
|
||||
highlighterEx.addMarkedOccurrenceHighlight(startOffset, endOffset, new SmartHighlightPainter());
|
||||
}
|
||||
}
|
||||
} catch (BadLocationException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Search through the text area and mark all occurrences that match the selected token.
|
||||
*
|
||||
* @param textArea the text area
|
||||
* @param classFileContainer the container
|
||||
* @param line the caret line
|
||||
* @param column the caret column
|
||||
* @param finalToken the token
|
||||
* @param highlighterEx the highlighter
|
||||
*/
|
||||
private static void markMethodLocalVariable(RSyntaxTextArea textArea, ClassFileContainer classFileContainer, int line, int column, Token finalToken, RSyntaxTextAreaHighlighterEx highlighterEx)
|
||||
{
|
||||
classFileContainer.methodLocalMembers.values().forEach(localVariables -> localVariables.forEach(localVariable -> {
|
||||
String method;
|
||||
if (localVariable.line == line && localVariable.columnStart - 1 <= column && localVariable.columnEnd >= column)
|
||||
{
|
||||
method = localVariable.method;
|
||||
try
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
for (ClassLocalVariableLocation location : classFileContainer.getLocalLocationsFor(finalToken.getLexeme()))
|
||||
{
|
||||
if (Objects.equals(method, location.method))
|
||||
{
|
||||
int startOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnStart - 1);
|
||||
int endOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnEnd - 1);
|
||||
|
||||
highlighterEx.addMarkedOccurrenceHighlight(startOffset, endOffset, new SmartHighlightPainter());
|
||||
}
|
||||
}
|
||||
} catch (BadLocationException ex)
|
||||
{
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void markClasses(RSyntaxTextArea textArea, ClassFileContainer classFileContainer, int line, int column, Token finalToken, RSyntaxTextAreaHighlighterEx highlighterEx)
|
||||
{
|
||||
classFileContainer.classReferences.values().forEach(classes -> classes.forEach(clazz -> {
|
||||
if (clazz.line == line && clazz.columnStart - 1 <= column && clazz.columnEnd - 1 >= column)
|
||||
{
|
||||
try
|
||||
{
|
||||
Element root = textArea.getDocument().getDefaultRootElement();
|
||||
for (ClassReferenceLocation location : classFileContainer.getClassReferenceLocationsFor(finalToken.getLexeme()))
|
||||
{
|
||||
int startOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnStart - 1);
|
||||
int endOffset = root.getElement(location.line - 1).getStartOffset() + (location.columnEnd - 1);
|
||||
highlighterEx.addMarkedOccurrenceHighlight(startOffset, endOffset, new SmartHighlightPainter());
|
||||
}
|
||||
} catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public class MarkerCaretListener implements CaretListener
|
||||
{
|
||||
private final String classContainerName;
|
||||
|
||||
public MarkerCaretListener(String classContainerName)
|
||||
{
|
||||
this.classContainerName = classContainerName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void caretUpdate(CaretEvent e)
|
||||
{
|
||||
SearchableRSyntaxTextArea textArea = (SearchableRSyntaxTextArea) e.getSource();
|
||||
RSyntaxTextAreaHighlighterEx highlighterEx = (RSyntaxTextAreaHighlighterEx) bytecodeViewPanel.textArea.getHighlighter();
|
||||
highlighterEx.clearMarkOccurrencesHighlights();
|
||||
markOccurrences(textArea, BytecodeViewer.viewer.workPane.classFiles.get(classContainerName), errorStripe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer;
|
||||
|
||||
import com.github.javaparser.ParseProblemException;
|
||||
import com.github.javaparser.StaticJavaParser;
|
||||
import com.github.javaparser.ast.CompilationUnit;
|
||||
import com.github.javaparser.resolution.TypeSolver;
|
||||
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
|
||||
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
|
||||
import com.github.javaparser.symbolsolver.resolution.typesolvers.JarTypeSolver;
|
||||
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
|
||||
import the.bytecode.club.bytecodeviewer.resources.ResourceContainer;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.*;
|
||||
import the.bytecode.club.bytecodeviewer.resources.classcontainer.parser.MyVoidVisitor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.NavigableMap;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* This is a container for a specific class. The container name is based on the actual class name and the decompiler used.
|
||||
* <p>
|
||||
* Created by Bl3nd.
|
||||
* Date: 8/26/2024
|
||||
*/
|
||||
public class ClassFileContainer
|
||||
{
|
||||
public transient NavigableMap<String, ArrayList<ClassFieldLocation>> fieldMembers = new TreeMap<>();
|
||||
public transient NavigableMap<String, ArrayList<ClassParameterLocation>> methodParameterMembers = new TreeMap<>();
|
||||
public transient NavigableMap<String, ArrayList<ClassLocalVariableLocation>> methodLocalMembers = new TreeMap<>();
|
||||
public transient NavigableMap<String, ArrayList<ClassMethodLocation>> methodMembers = new TreeMap<>();
|
||||
public transient NavigableMap<String, ArrayList<ClassReferenceLocation>> classReferences = new TreeMap<>();
|
||||
|
||||
public boolean hasBeenParsed = false;
|
||||
public final String className;
|
||||
private final String content;
|
||||
private final String parentContainer;
|
||||
private final String path;
|
||||
|
||||
public ClassFileContainer(String className, String content, ResourceContainer resourceContainer)
|
||||
{
|
||||
this.className = className;
|
||||
this.content = content;
|
||||
this.parentContainer = resourceContainer.name;
|
||||
this.path = resourceContainer.file.getAbsolutePath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the class content with JavaParser.
|
||||
*/
|
||||
public void parse()
|
||||
{
|
||||
try
|
||||
{
|
||||
TypeSolver typeSolver = new CombinedTypeSolver(new ReflectionTypeSolver(false), new JarTypeSolver(path));
|
||||
StaticJavaParser.getParserConfiguration().setSymbolResolver(new JavaSymbolSolver(typeSolver));
|
||||
CompilationUnit compilationUnit = StaticJavaParser.parse(this.content);
|
||||
compilationUnit.accept(new MyVoidVisitor(this, compilationUnit), null);
|
||||
} catch (ParseProblemException e)
|
||||
{
|
||||
System.err.println("Parsing error!");
|
||||
} catch (IOException e)
|
||||
{
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getName()
|
||||
{
|
||||
return this.className.substring(this.className.lastIndexOf('/') + 1, this.className.lastIndexOf('.'));
|
||||
}
|
||||
|
||||
public String getDecompiler()
|
||||
{
|
||||
return this.className.substring(this.className.lastIndexOf('-') + 1);
|
||||
}
|
||||
|
||||
public String getParentContainer()
|
||||
{
|
||||
return this.parentContainer;
|
||||
}
|
||||
|
||||
public void putField(String key, ClassFieldLocation value)
|
||||
{
|
||||
this.fieldMembers.computeIfAbsent(key, v -> new ArrayList<>()).add(value);
|
||||
}
|
||||
|
||||
public List<ClassFieldLocation> getFieldLocationsFor(String fieldName)
|
||||
{
|
||||
return fieldMembers.getOrDefault(fieldName, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void putParameter(String key, ClassParameterLocation value)
|
||||
{
|
||||
this.methodParameterMembers.computeIfAbsent(key, v -> new ArrayList<>()).add(value);
|
||||
}
|
||||
|
||||
public List<ClassParameterLocation> getParameterLocationsFor(String key)
|
||||
{
|
||||
return methodParameterMembers.getOrDefault(key, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void putLocalVariable(String key, ClassLocalVariableLocation value)
|
||||
{
|
||||
this.methodLocalMembers.computeIfAbsent(key, v -> new ArrayList<>()).add(value);
|
||||
}
|
||||
|
||||
public List<ClassLocalVariableLocation> getLocalLocationsFor(String key)
|
||||
{
|
||||
return methodLocalMembers.getOrDefault(key, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void putMethod(String key, ClassMethodLocation value)
|
||||
{
|
||||
this.methodMembers.computeIfAbsent(key, v -> new ArrayList<>()).add(value);
|
||||
}
|
||||
|
||||
public List<ClassMethodLocation> getMethodLocationsFor(String key)
|
||||
{
|
||||
return methodMembers.getOrDefault(key, new ArrayList<>());
|
||||
}
|
||||
|
||||
public void putClassReference(String key, ClassReferenceLocation value)
|
||||
{
|
||||
this.classReferences.computeIfAbsent(key, v -> new ArrayList<>()).add(value);
|
||||
}
|
||||
|
||||
public List<ClassReferenceLocation> getClassReferenceLocationsFor(String key)
|
||||
{
|
||||
return classReferences.getOrDefault(key, null);
|
||||
}
|
||||
|
||||
public String getClassForField(String fieldName)
|
||||
{
|
||||
AtomicReference<String> className = new AtomicReference<>("");
|
||||
this.classReferences.forEach((s, v) -> {
|
||||
v.forEach(classReferenceLocation -> {
|
||||
if (classReferenceLocation.fieldName.equals(fieldName))
|
||||
{
|
||||
className.set(classReferenceLocation.packagePath + "/" + s);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return className.get();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer.locations;
|
||||
|
||||
/**
|
||||
* Created by Bl3nd.
|
||||
* Date: 8/26/2024
|
||||
*/
|
||||
public class ClassFieldLocation
|
||||
{
|
||||
public final String owner;
|
||||
public final String type;
|
||||
public final int line;
|
||||
public final int columnStart;
|
||||
public final int columnEnd;
|
||||
|
||||
public ClassFieldLocation(final String owner, final String type, final int line, final int columnStart, final int columnEnd)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.type = type;
|
||||
this.line = line;
|
||||
this.columnStart = columnStart;
|
||||
this.columnEnd = columnEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ClassFieldLocation{" + "owner='" + owner + '\'' + ", type='" + type + '\'' + ", line=" + line + ", columnStart=" + columnStart + ", columnEnd=" + columnEnd + '}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer.locations;
|
||||
|
||||
/**
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/5/2024
|
||||
*/
|
||||
public class ClassLocalVariableLocation
|
||||
{
|
||||
public final String owner;
|
||||
public final String method;
|
||||
public final String decRef;
|
||||
public final int line;
|
||||
public final int columnStart;
|
||||
public final int columnEnd;
|
||||
|
||||
public ClassLocalVariableLocation(String owner, String method, String decRef, int line, int columnStart, int columnEnd)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.method = method;
|
||||
this.decRef = decRef;
|
||||
this.line = line;
|
||||
this.columnStart = columnStart;
|
||||
this.columnEnd = columnEnd;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer.locations;
|
||||
|
||||
/**
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/5/2024
|
||||
*/
|
||||
public class ClassMethodLocation
|
||||
{
|
||||
public final String owner;
|
||||
public final String signature;
|
||||
public final String methodParameterTypes;
|
||||
public final String decRef;
|
||||
public final int line;
|
||||
public final int columnStart;
|
||||
public final int columnEnd;
|
||||
|
||||
public ClassMethodLocation(String owner, String signature, String methodParameterTypes, String decRef, int line, int columnStart, int columnEnd)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.signature = signature;
|
||||
this.methodParameterTypes = methodParameterTypes;
|
||||
this.decRef = decRef;
|
||||
this.line = line;
|
||||
this.columnStart = columnStart;
|
||||
this.columnEnd = columnEnd;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer.locations;
|
||||
|
||||
/**
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/5/2024
|
||||
*/
|
||||
public class ClassParameterLocation
|
||||
{
|
||||
public final String owner;
|
||||
public final String method;
|
||||
public final String decRef;
|
||||
public final int line;
|
||||
public final int columnStart;
|
||||
public final int columnEnd;
|
||||
|
||||
public ClassParameterLocation(String owner, String method, String decRef, int line, int columnStart, int columnEnd)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.method = method;
|
||||
this.decRef = decRef;
|
||||
this.line = line;
|
||||
this.columnStart = columnStart;
|
||||
this.columnEnd = columnEnd;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer.locations;
|
||||
|
||||
/**
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/20/2024
|
||||
*/
|
||||
public class ClassReferenceLocation
|
||||
{
|
||||
public final String owner;
|
||||
public final String packagePath;
|
||||
public final String fieldName;
|
||||
public final String type;
|
||||
public final int line;
|
||||
public final int columnStart;
|
||||
public final int columnEnd;
|
||||
|
||||
public ClassReferenceLocation(String owner, String packagePath, String fieldName, String type, int line, int columnStart, int columnEnd)
|
||||
{
|
||||
this.owner = owner;
|
||||
this.packagePath = packagePath;
|
||||
this.fieldName = fieldName;
|
||||
this.type = type;
|
||||
this.line = line;
|
||||
this.columnStart = columnStart;
|
||||
this.columnEnd = columnEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return "ClassClassLocation{" + "owner='" + owner + '\'' + ", fieldName='" + fieldName + '\'' + ", type='" + type + '\'' + ", line=" + line + ", columnStart=" + columnStart + ", columnEnd=" + columnEnd + '}';
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,31 @@
|
|||
package the.bytecode.club.bytecodeviewer.resources.classcontainer.parser;
|
||||
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Created by Bl3nd.
|
||||
* Date: 9/5/2024
|
||||
*/
|
||||
public class TokenUtil
|
||||
{
|
||||
public static Token getToken(final RSyntaxTextArea textArea, final @NotNull Token token)
|
||||
{
|
||||
String lexeme = token.getLexeme();
|
||||
return lexeme.isEmpty()
|
||||
|| lexeme.equals(".")
|
||||
|| lexeme.equals("(")
|
||||
|| lexeme.equals(")")
|
||||
|| lexeme.equals("[")
|
||||
|| lexeme.equals("~")
|
||||
|| lexeme.equals("-")
|
||||
|| lexeme.equals("+")
|
||||
|| lexeme.equals(" ")
|
||||
|| lexeme.equals(";")
|
||||
|| lexeme.equals(",")
|
||||
|| lexeme.equals(">")
|
||||
? textArea.modelToToken(textArea.getCaretPosition() - 1)
|
||||
: token;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user