package jadx.gui.ui;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.gui.JadxWrapper;
import jadx.gui.treemodel.JNode;
import jadx.gui.utils.NLS;
import jadx.gui.utils.NameIndex;
import jadx.gui.utils.Position;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIDefaults;
import javax.swing.UIManager;
import javax.swing.WindowConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
public class SearchDialog extends JDialog {
private static final long serialVersionUID = -5105405456969134105L;
private static final int MAX_RESULTS_COUNT = 100;
private static enum SearchOptions {
CLASS,
METHOD,
FIELD,
CODE
}
private static final Set<SearchOptions> OPTIONS =
EnumSet.of(SearchOptions.CLASS, SearchOptions.METHOD, SearchOptions.FIELD);
private final TabbedPane tabbedPane;
private final JadxWrapper wrapper;
private NameIndex<JavaNode> index;
private JTextField searchField;
private ResultsModel resultsModel;
private JList resultsList;
private JProgressBar busyBar;
public SearchDialog(Frame owner, TabbedPane tabbedPane, JadxWrapper wrapper) {
super(owner);
this.tabbedPane = tabbedPane;
this.wrapper = wrapper;
initUI();
}
public void prepare() {
LoadTask task = new LoadTask();
task.init();
task.execute();
}
private void loadData() {
index = new NameIndex<JavaNode>();
for (JavaClass cls : wrapper.getClasses()) {
indexClass(cls);
}
}
private synchronized void performSearch() {
String text = searchField.getText();
List<JavaNode> results;
if (text == null || text.isEmpty() || index == null) {
results = Collections.emptyList();
} else {
results = index.search(text);
}
resultsModel.setResults(results);
}
private void openSelectedItem() {
int selectedId = resultsList.getSelectedIndex();
if (selectedId == -1) {
return;
}
JNode node = (JNode) resultsModel.get(selectedId);
tabbedPane.showCode(new Position(node.getRootClass(), node.getLine()));
dispose();
}
private void indexClass(JavaClass cls) {
if (OPTIONS.contains(SearchOptions.CLASS)) {
index.add(cls.getFullName(), cls);
}
if (OPTIONS.contains(SearchOptions.METHOD)) {
for (JavaMethod mth : cls.getMethods()) {
index.add(mth.getFullName(), mth);
}
}
if (OPTIONS.contains(SearchOptions.FIELD)) {
for (JavaField fld : cls.getFields()) {
index.add(fld.getFullName(), fld);
}
}
if (OPTIONS.contains(SearchOptions.CODE)) {
String code = cls.getCode();
index.add(code, cls);
}
for (JavaClass innerCls : cls.getInnerClasses()) {
indexClass(innerCls);
}
}
private class LoadTask extends SwingWorker<Void, Void> {
public void init() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
busyBar.setVisible(true);
searchField.setEnabled(false);
resultsList.setEnabled(false);
setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
});
}
@Override
public Void doInBackground() {
loadData();
return null;
}
@Override
public void done() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
setCursor(null);
searchField.setEnabled(true);
resultsList.setEnabled(true);
busyBar.setVisible(false);
}
});
}
}
private static class ResultsModel extends DefaultListModel {
private static final long serialVersionUID = -7821286846923903208L;
private void setResults(List<JavaNode> results) {
removeAllElements();
if (results.isEmpty()) {
return;
}
int count = Math.min(results.size(), MAX_RESULTS_COUNT);
for (int i = 0; i < count; i++) {
addElement(JNode.makeFrom(results.get(i)));
}
}
}
private static class ResultsCellRenderer implements ListCellRenderer {
private final Color selectedBackground;
private final Color selectedForeground;
ResultsCellRenderer() {
UIDefaults defaults = UIManager.getDefaults();
selectedBackground = defaults.getColor("List.selectionBackground");
selectedForeground = defaults.getColor("List.selectionForeground");
}
@Override
public Component getListCellRendererComponent(JList list,
Object obj, int index, boolean isSelected, boolean cellHasFocus) {
if (!(obj instanceof JNode)) {
return null;
}
JNode value = (JNode) obj;
JLabel label = new JLabel();
label.setOpaque(true);
label.setIcon(value.getIcon());
label.setText(value.makeLongString());
if (isSelected) {
label.setBackground(selectedBackground);
label.setForeground(selectedForeground);
}
return label;
}
}
private class SearchFieldListener implements DocumentListener {
public void changedUpdate(DocumentEvent e) {
performSearch();
}
public void removeUpdate(DocumentEvent e) {
performSearch();
}
public void insertUpdate(DocumentEvent e) {
performSearch();
}
}
private void initUI() {
JLabel findLabel = new JLabel(NLS.str("search_dialog.open_by_name"));
searchField = new JTextField();
searchField.setAlignmentX(LEFT_ALIGNMENT);
searchField.getDocument().addDocumentListener(new SearchFieldListener());
JCheckBox clsChBox = makeOptionsCheckBox(NLS.str("search_dialog.class"), SearchOptions.CLASS);
JCheckBox mthChBox = makeOptionsCheckBox(NLS.str("search_dialog.method"), SearchOptions.METHOD);
JCheckBox fldChBox = makeOptionsCheckBox(NLS.str("search_dialog.field"), SearchOptions.FIELD);
JCheckBox codeChBox = makeOptionsCheckBox(NLS.str("search_dialog.code"), SearchOptions.CODE);
codeChBox.setEnabled(false);
resultsModel = new ResultsModel();
resultsList = new JList(resultsModel);
resultsList.setCellRenderer(new ResultsCellRenderer());
resultsList.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 2) {
openSelectedItem();
}
}
});
JPanel searchOptions = new JPanel(new FlowLayout(FlowLayout.LEFT));
searchOptions.setBorder(BorderFactory.createTitledBorder(NLS.str("search_dialog.search_in")));
searchOptions.add(clsChBox);
searchOptions.add(mthChBox);
searchOptions.add(fldChBox);
searchOptions.add(codeChBox);
searchOptions.setAlignmentX(LEFT_ALIGNMENT);
JPanel searchPane = new JPanel();
searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.PAGE_AXIS));
findLabel.setLabelFor(searchField);
searchPane.add(findLabel);
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
searchPane.add(searchField);
searchPane.add(Box.createRigidArea(new Dimension(0, 5)));
searchPane.add(searchOptions);
searchPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JPanel listPane = new JPanel();
listPane.setLayout(new BoxLayout(listPane, BoxLayout.PAGE_AXIS));
listPane.add(new JScrollPane(resultsList));
listPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
busyBar = new JProgressBar();
busyBar.setIndeterminate(true);
busyBar.setVisible(false);
//Create and initialize the buttons.
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
cancelButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
dispose();
}
});
JButton openBtn = new JButton(NLS.str("search_dialog.open"));
openBtn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
openSelectedItem();
}
});
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(busyBar);
searchPane.add(Box.createRigidArea(new Dimension(5, 0)));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(openBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
Container contentPane = getContentPane();
contentPane.add(searchPane, BorderLayout.PAGE_START);
contentPane.add(listPane, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END);
getRootPane().setDefaultButton(openBtn);
setTitle(NLS.str("menu.search"));
pack();
setSize(700, 500);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.APPLICATION_MODAL);
}
private JCheckBox makeOptionsCheckBox(String name, final SearchOptions opt) {
JCheckBox chBox = new JCheckBox(name);
chBox.setAlignmentX(LEFT_ALIGNMENT);
chBox.setSelected(OPTIONS.contains(opt));
chBox.addItemListener(new ItemListener() {
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED) {
OPTIONS.add(opt);
} else {
OPTIONS.remove(opt);
}
loadData();
performSearch();
}
});
return chBox;
}
}