// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.check;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.ProgressMonitor;
import org.infinity.NearInfinity;
import org.infinity.datatype.ResourceRef;
import org.infinity.datatype.SpawnResourceRef;
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.cre.CreResource;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.resource.text.PlainTextResource;
import org.infinity.search.ReferenceHitFrame;
import org.infinity.util.Debugging;
import org.infinity.util.Misc;
public final class ResRefChecker extends ChildFrame implements ActionListener, Runnable
{
private static final String FMT_PROGRESS = "Checking %ss...";
private static final String[] FILETYPES = {"ARE", "CHR", "CHU", "CRE", "DLG", "EFF", "GAM", "ITM", "PRO",
"SPL", "STO", "VEF", "VVC", "WED", "WMP"};
private final JButton bstart = new JButton("Check", Icons.getIcon(Icons.ICON_FIND_16));
private final JButton bcancel = new JButton("Cancel", Icons.getIcon(Icons.ICON_DELETE_16));
private final JButton binvert = new JButton("Invert", Icons.getIcon(Icons.ICON_REFRESH_16));
private final JCheckBox[] boxes = new JCheckBox[FILETYPES.length];
private final ReferenceHitFrame hitFrame;
private List<ResourceEntry> files;
private List<String> extraValues;
private ProgressMonitor progress;
private int progressIndex;
public ResRefChecker()
{
super("ResRef Checker");
setIconImage(Icons.getIcon(Icons.ICON_REFRESH_16).getImage());
hitFrame = new ReferenceHitFrame("Illegal ResourceRefs", NearInfinity.getInstance());
ResourceEntry spawnRef = ResourceFactory.getResourceEntry("SPAWNGRP.2DA");
if (spawnRef != null) {
PlainTextResource spawn = (PlainTextResource)ResourceFactory.getResource(spawnRef);
extraValues = spawn.extract2DAHeaders();
}
bstart.setMnemonic('s');
bcancel.setMnemonic('c');
binvert.setMnemonic('i');
bstart.addActionListener(this);
bcancel.addActionListener(this);
binvert.addActionListener(this);
getRootPane().setDefaultButton(bstart);
JPanel boxpanel = new JPanel(new GridLayout(0, 2, 3, 3));
for (int i = 0; i < boxes.length; i++) {
boxes[i] = new JCheckBox(FILETYPES[i], true);
boxpanel.add(boxes[i]);
}
boxpanel.setBorder(BorderFactory.createEmptyBorder(3, 12, 3, 0));
JPanel ipanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
ipanel.add(binvert);
JPanel innerpanel = new JPanel(new BorderLayout());
innerpanel.add(boxpanel, BorderLayout.CENTER);
innerpanel.add(ipanel, BorderLayout.SOUTH);
innerpanel.setBorder(BorderFactory.createTitledBorder("Select files to check:"));
JPanel bpanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
bpanel.add(bstart);
bpanel.add(bcancel);
JPanel mainpanel = new JPanel(new BorderLayout());
mainpanel.add(innerpanel, BorderLayout.CENTER);
mainpanel.add(bpanel, BorderLayout.SOUTH);
mainpanel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
JPanel pane = (JPanel)getContentPane();
pane.setLayout(new BorderLayout());
pane.add(mainpanel, BorderLayout.CENTER);
pack();
Center.center(this, NearInfinity.getInstance().getBounds());
setVisible(true);
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bstart) {
setVisible(false);
files = new ArrayList<ResourceEntry>();
for (int i = 0; i < FILETYPES.length; i++) {
if (boxes[i].isSelected())
files.addAll(ResourceFactory.getResources(FILETYPES[i]));
}
if (files.size() > 0)
new Thread(this).start();
}
else if (event.getSource() == binvert) {
for (final JCheckBox box : boxes)
box.setSelected(!box.isSelected());
}
else if (event.getSource() == bcancel)
setVisible(false);
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface Runnable ---------------------
@Override
public void run()
{
try {
String type = "WWWW";
progressIndex = 0;
progress = new ProgressMonitor(NearInfinity.getInstance(), "Checking...",
String.format(FMT_PROGRESS, type),
0, files.size());
progress.setMillisToDecideToPopup(100);
ThreadPoolExecutor executor = Misc.createThreadPool();
boolean isCancelled = false;
Debugging.timerReset();
for (int i = 0; i < files.size(); i++) {
ResourceEntry entry = files.get(i);
if (i % 10 == 0) {
String ext = entry.getExtension();
if (ext != null && !type.equalsIgnoreCase(ext)) {
type = ext;
progress.setNote(String.format(FMT_PROGRESS, type));
}
}
Misc.isQueueReady(executor, true, -1);
executor.execute(new Worker(entry));
if (progress.isCanceled()) {
isCancelled = true;
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) {
hitFrame.close();
JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Check cancelled", "Info",
JOptionPane.INFORMATION_MESSAGE);
} else {
hitFrame.setVisible(true);
}
} finally {
advanceProgress(true);
}
Debugging.timerShow("Check completed", Debugging.TimeFormat.MILLISECONDS);
}
// --------------------- End Interface Runnable ---------------------
private void search(ResourceEntry entry, AbstractStruct struct)
{
List<StructEntry> structList = struct.getFlatList();
for (int i = 0; i < structList.size(); i++) {
Object o = structList.get(i);
if (o instanceof SpawnResourceRef) {
SpawnResourceRef ref = (SpawnResourceRef)o;
String resourceName = ref.getResourceName();
if (resourceName.equalsIgnoreCase("None")) {
// ignore
} else if (extraValues != null && extraValues.contains(ref.getText())) {
// ignore
} else if (!ResourceFactory.resourceExists(resourceName)) {
synchronized (hitFrame) {
hitFrame.addHit(entry, entry.getSearchString(), ref);
}
} else if (!ref.isLegalEntry(ResourceFactory.getResourceEntry(resourceName))) {
synchronized (hitFrame) {
hitFrame.addHit(entry, entry.getSearchString(), ref);
}
}
}
else if (o instanceof ResourceRef) {
ResourceRef ref = (ResourceRef)o;
String resourceName = ref.getResourceName();
if (resourceName.equalsIgnoreCase("None")) {
// ignore
} else if (struct instanceof CreResource && resourceName.substring(0, 3).equalsIgnoreCase("rnd")) {
// ignore
} else if (!ResourceFactory.resourceExists(resourceName)) {
synchronized (hitFrame) {
hitFrame.addHit(entry, entry.getSearchString(), ref);
}
} else if (!ref.isLegalEntry(ResourceFactory.getResourceEntry(resourceName))) {
synchronized (hitFrame) {
hitFrame.addHit(entry, entry.getSearchString(), ref);
}
}
}
}
}
private synchronized void advanceProgress(boolean finished)
{
if (progress != null) {
if (finished) {
progressIndex = 0;
progress.close();
progress = null;
} else {
progressIndex++;
progress.setProgress(progressIndex);
}
}
}
//-------------------------- 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) {
search(entry, (AbstractStruct)resource);
}
}
advanceProgress(false);
}
}
}