// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.search;
import java.awt.Component;
import java.awt.Container;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.ProgressMonitor;
import org.infinity.datatype.StringRef;
import org.infinity.gui.Center;
import org.infinity.gui.ChildFrame;
import org.infinity.icon.Icons;
import org.infinity.resource.AbstractStruct;
import org.infinity.resource.Resource;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.resource.bcs.Compiler;
import org.infinity.resource.bcs.Decompiler;
import org.infinity.resource.dlg.AbstractCode;
import org.infinity.resource.dlg.Action;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.util.Debugging;
import org.infinity.util.Misc;
public final class DialogSearcher implements Runnable, ActionListener
{
private static final String FMT_PROGRESS = "Processing resource %d/%d";
private final ChildFrame inputFrame;
private final Component parent;
private final JButton bsearch = new JButton("Search", Icons.getIcon(Icons.ICON_FIND_AGAIN_16));
private final JCheckBox cbwhole = new JCheckBox("Match whole word only");
private final JCheckBox cbcase = new JCheckBox("Match case");
private final JCheckBox cbsearchcode = new JCheckBox("Include trigger & action scripts", true);
private final JCheckBox cbregex = new JCheckBox("Use regular expressions");
private final JTextField tfinput = new JTextField("", 15);
private final List<ResourceEntry> files;
private ProgressMonitor progress;
private int progressIndex;
private Pattern regPattern;
private ReferenceHitFrame resultFrame;
public DialogSearcher(List<ResourceEntry> files, Component parent)
{
this.files = files;
this.parent = parent;
String title = "Find: DLG files";
if (files.size() == 1)
title = "Find: " + files.get(0).toString();
inputFrame = new ChildFrame(title, true);
inputFrame.setIconImage(Icons.getIcon(Icons.ICON_FIND_16).getImage());
inputFrame.getRootPane().setDefaultButton(bsearch);
bsearch.addActionListener(this);
tfinput.addActionListener(this);
Container pane = inputFrame.getContentPane();
GridBagLayout gbl = new GridBagLayout();
GridBagConstraints gbc = new GridBagConstraints();
pane.setLayout(gbl);
JLabel label = new JLabel("Find what:");
label.setLabelFor(tfinput);
label.setDisplayedMnemonic('f');
JPanel matchpanel = new JPanel();
matchpanel.setLayout(new GridLayout(2, 2));
matchpanel.add(cbwhole);
matchpanel.add(cbsearchcode);
matchpanel.add(cbcase);
matchpanel.add(cbregex);
cbwhole.setMnemonic('w');
cbcase.setMnemonic('m');
cbregex.setMnemonic('r');
gbc.insets = new Insets(6, 6, 3, 3);
gbc.weightx = 0.0;
gbc.weighty = 0.0;
gbc.fill = GridBagConstraints.NONE;
gbl.setConstraints(label, gbc);
pane.add(label);
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1.0;
gbc.gridwidth = 2;
gbc.insets.left = 3;
gbl.setConstraints(tfinput, gbc);
pane.add(tfinput);
gbc.weightx = 0.0;
gbc.insets.right = 6;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbl.setConstraints(bsearch, gbc);
pane.add(bsearch);
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.insets = new Insets(3, 6, 6, 6);
gbl.setConstraints(matchpanel, gbc);
pane.add(matchpanel);
inputFrame.pack();
Center.center(inputFrame, parent.getBounds());
inputFrame.setVisible(true);
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bsearch || event.getSource() == tfinput) {
inputFrame.setVisible(false);
new Thread(this).start();
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface Runnable ---------------------
@Override
public void run()
{
String term = tfinput.getText();
if (!cbregex.isSelected()) {
term = term.replaceAll("(\\W)", "\\\\$1");
}
if (cbwhole.isSelected()) {
term = ".*\\b" + term + "\\b.*";
} else {
term = ".*" + term + ".*";
}
try {
if (cbcase.isSelected()) {
regPattern = Pattern.compile(term, Pattern.DOTALL);
} else {
regPattern = Pattern.compile(term, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
}
} catch (PatternSyntaxException e) {
JOptionPane.showMessageDialog(parent, "Syntax error in search string.", "Error", JOptionPane.ERROR_MESSAGE);
regPattern = null;
return;
}
try {
// executing multithreaded search
boolean isCancelled = false;
inputFrame.setVisible(false);
resultFrame = new ReferenceHitFrame(term, parent);
ThreadPoolExecutor executor = Misc.createThreadPool();
progressIndex = 0;
progress = new ProgressMonitor(parent, "Searching...",
String.format(FMT_PROGRESS, files.size(), files.size()),
0, files.size());
progress.setNote(String.format(FMT_PROGRESS, progressIndex, files.size()));
progress.setMillisToDecideToPopup(100);
Debugging.timerReset();
for (int i = 0; i < files.size(); i++) {
Misc.isQueueReady(executor, true, -1);
executor.execute(new Worker(files.get(i)));
if (progress.isCanceled()) {
isCancelled = true;
// JOptionPane.showMessageDialog(parent, "Search canceled", "Info", JOptionPane.INFORMATION_MESSAGE);
break;
}
}
// enforcing thread termination if process has been cancelled
if (isCancelled) {
executor.shutdownNow();
} else {
executor.shutdown();
}
// waiting for pending threads to terminate
while (!executor.isTerminated()) {
if (!isCancelled && progress.isCanceled()) {
executor.shutdownNow();
isCancelled = true;
}
try { Thread.sleep(1); } catch (InterruptedException e) {}
}
if (isCancelled) {
resultFrame.close();
JOptionPane.showMessageDialog(parent, "Search cancelled", "Info", JOptionPane.INFORMATION_MESSAGE);
} else {
resultFrame.setVisible(true);
}
} finally {
advanceProgress(true);
regPattern = null;
resultFrame = null;
}
Debugging.timerShow("Search completed", Debugging.TimeFormat.MILLISECONDS);
}
// --------------------- End Interface Runnable ---------------------
private Map<StructEntry, StructEntry> makeSearchMap(AbstractStruct struct)
{
SortedMap<StructEntry, StructEntry> map = new TreeMap<StructEntry, StructEntry>();
for (int i = 0; i < struct.getFieldCount(); i++) {
StructEntry entry = struct.getField(i);
if (entry instanceof AbstractStruct)
map.putAll(makeSearchMap((AbstractStruct)entry));
else if (cbsearchcode.isSelected() && entry instanceof AbstractCode)
map.put(entry, entry);
else if (entry instanceof StringRef)
map.put(entry, struct);
}
return map;
}
private synchronized void advanceProgress(boolean finished)
{
if (progress != null) {
if (finished) {
progressIndex = 0;
progress.close();
progress = null;
} else {
progressIndex++;
if (progressIndex % 100 == 0) {
progress.setNote(String.format(FMT_PROGRESS, progressIndex, files.size()));
}
progress.setProgress(progressIndex);
}
}
}
private synchronized void addResult(ResourceEntry entry, String line, StructEntry ref)
{
if (resultFrame != null) {
resultFrame.addHit(entry, line, ref);
}
}
private Pattern getPattern()
{
return regPattern;
}
//-------------------------- INNER CLASSES --------------------------
private class Worker implements Runnable
{
private final ResourceEntry entry;
public Worker(ResourceEntry entry)
{
this.entry = entry;
}
@Override
public void run()
{
if (entry != null) {
Resource resource = ResourceFactory.getResource(entry);
if (resource != null) {
Map<StructEntry, StructEntry> searchMap = makeSearchMap((AbstractStruct)resource);
for (final StructEntry searchEntry : searchMap.keySet()) {
String s = null;
if (searchEntry instanceof StringRef) {
s = searchEntry.toString();
} else if (searchEntry instanceof AbstractCode) {
try {
Compiler compiler = new Compiler(searchEntry.toString(),
(searchEntry instanceof Action) ? Compiler.ScriptType.ACTION :
Compiler.ScriptType.TRIGGER);
String code = compiler.getCode();
if (compiler.getErrors().size() == 0) {
Decompiler decompiler = new Decompiler(code, false);
if (searchEntry instanceof Action) {
decompiler.setScriptType(Decompiler.ScriptType.ACTION);
} else {
decompiler.setScriptType(Decompiler.ScriptType.TRIGGER);
}
s = decompiler.getSource();
} else {
synchronized (System.out) {
System.out.println("Error(s) compiling " + entry.toString() + " - " + searchEntry.getName());
}
}
} catch (Exception e) {
synchronized (System.out) {
System.out.println("Exception (de)compiling " + entry.toString() + " - " + searchEntry.getName());
}
e.printStackTrace();
}
if (s == null) {
s = "";
}
}
Matcher matcher = getPattern().matcher(s);
if (matcher.matches()) {
addResult(entry, searchMap.get(searchEntry).getName(), searchEntry);
}
}
}
}
advanceProgress(false);
}
}
}